Functional component returns object Object in react - reactjs

I want to output the values of a json object. On each fetch, the type of values change from string to null. That's why I have the for loop and the if statement. Each constant inside the loop outputs the correct value but after it is added to listItems each <li> just returns [object Object].
I tried using <li>${value} (${time})</li> but this outputs the tags <li> as a string value.
function ValuesList(props) {
let listItems = '';
for(let i=1; i<=15; i+=1){
const value = props.myDrink[`strValue` + i]; *//json code is: strValue1, strValue2, ... strValue15, with some of null value.*
const time = props.myDrink[`strTime` + i];
if (typeof value === 'string'){
listItems += <li>{value} ({time})</li>
}
}
return (<ul>{listItems}</ul>)
};
I have done this in jQuery before but I need to make it work in react. Should I change the +=? If not, what line should I fix? This is part of my first react project. Please help, thank you :)

Rather than concatenating JSX values into an empty string, push them into an array (with a key attribute):
let listItems = [];
for(let i=1; i<=15; i+=1){
const value = props.myDrink[`strValue` + i];
const time = props.myDrink[`strTime` + i];
if (typeof value === 'string'){
listItems.push(<li key={/* some unique key. Probably your value */}>{value} ({time})</li>);
}
}

You should directly map your items. There is no need to do what you used to do in jQuery.
if your drinks prop is an array
return (
<ul>
{props.drinks(map(({value, time}, i)=>{
if (typeof value === 'string'){
return <li key={`item-${i}`}>{value} ({time})</li>
}
})
</ul>
)
if your drinks props is an object
return (
<ul>
{Object.entires(props.drinks).map(({ value, time }, i) => {
if (typeof value === "string") {
return <li key={`item-${i}`}>{value} ({time})</li>;
}
})}
</ul>
)

Related

can't detect links by tagName app.js in react

I'm trying to grab some elements when they are links so I can check their href values. I'm trying to run some code that passes through all the nodes and childNodes in my app then whenever I find an <a> tag but I can't seem to detect them using my current logic.
I looked around and I should be able to get element type using element.tagName, perhaps something is wrong with my loop structure? Hoping someone can help me out or give me some pointers.
My JSX:
<div id="app">
<p>thats dope</p>
<div>
some stuff here
<img src = {deer} width="80px" height="80px" alt="a deer"></img>
</div>
<div>
<p>here there will be a sample node to check out</p>
link
<TestNode/>
</div>
</div>
my function is as follows:
checkNodes= (id,escapeChars,whiteListURLS) =>{
var app = document.getElementById(id)
if (app.hasChildNodes()) {
let children = app.childNodes;
//check every child in the dom within the passed in ID
for (let i = 0; i < children.length; i++) {
console.log(children[i])
for(let x = 0; x < escapeChars.length; x++ ){
if(children[i].nodeValue !== undefined && children[i].nodeValue === escapeChars[x] ){
console.log('error detected escape chars ',children[i])
return false
}
//CHECK FOR <A> TAG
if(children[i].tagName.toLowerCase() === "a"){
console.log('this is a link')
if(children[i].getAttribute("href") !== whiteListURLS[x]){
console.log('error detected suspect url ',children[i])
}
}
}
//check all children inside a child
for(let j = 0; j < children[i].length; j++){
console.log(children[i][j])
for(let z = 0; z < escapeChars.length; z++ ){
if(children[i][j].nodeValue !== undefined && children[i][j].nodeValue === escapeChars[z]){
console.log('error detected escape chars ',children[i][j])
return false
}
//CHECK FOR <A> TAG
if(children[i][j].tagName.toLowerCase() === "a") {
console.log('this is a link')
if(children[i][j].getAttribute("href") !== whiteListURLS[z]){
console.log('error detected suspect url ',children[i][j])
}
}
}
}
}
}
}
I suggest you to use document.querySelectorAll which is much easier and cleaner. It will help you to query only the a tags. https://developer.mozilla.org/en-US/docs/Web/API/Document/querySelectorAll
const checkNodes= (id,escapeChars,whiteListURLS) =>{
const root = document.getElementById("root");
const anchors = root.querySelectorAll('a'); // find all a tags within root node
console.log(anchors);
for (let i = 0; i < anchors.length; i++) {
console.log(anchors[i].href);
}
}

ReactJS : Using function as props to return value for <td> gives <Component/> error

I have the following function:
budgetList() {
return this.state.projects.map(currentproject=> {
return <Budget project={currentproject} filter= {this.filterIssues} key={currentproject._id} stop={this.stopPropagation} modal={this.state.modal} toggle={this.toggle}/>;
})
}
My issue comes from the filterIssues function which is used to filter through the state and retrieve some values. Posted below.
filterIssues (id, sprint) {
let filteredIssues = this.state.jiras
.filter( issue => issue.fields.project.key === id )
.filter( issue => issue.fields.sprint.name === sprint )
let sum = 0;
for (var i = 0; i < filteredIssues.length; i++) {
sum += filteredIssues[i].fields.timetracking.originalEstimateSeconds
}
sum /= 3600;
console.log(sum)
return sum;
}
Now I am using the function to retrieve a value for each in the Budget element like so.
const Budget = props => (
<tr onClick={() => props.toggle(props.project._id)}>
<td>£{((props.project.proposedBudget / 4) *1000) }</td>
<td>{() => props.filter(props.project.project, "APP" + 74)}</td>
<td>{() => props.filter(props.project.project, "APP" + 75)}</td>
</tr>
)
The problem is that for every
Functions are not valid as a React child. This may happen if you
return a Component instead of from render. Or maybe you
meant to call this function rather than return it.
Not exactly sure what I am doing wrong. Function works fine on its own outside the Budget component.

React not re-rendering when state changed from hook

Edit: Thanks for the help everyone. I needed to change the reference of the array and fixed it by doing:
setData([...sorted])
I am currently rendering out a list of tasks. This is a snippet of my return function within a functional component:
const [ data, setData ] = useState( mockData )
<tbody>
{ data.map(d => <TaskItem key={d.claimable} task={d}/>) }
</tbody>
When I click on a certain button on the page, the dataset gets sorted and I call setData(sortedData)
For some reason, the table isnt being re-rendered with the sorted data. Is there something I did wrong here?
This is the sort function:
function filterByContactAmount():void {
let sorted = data.sort((a:any, b:any) => {
let aTimesContacted:number = a.data.person.contact.list.reduce((acc:number, val:any):number => acc + val.history.length, 0)
let bTimesContacted:number = b.data.person.contact.list.reduce((acc:number, val:any):number => acc + val.history.length, 0)
if ( aTimesContacted > bTimesContacted ) {
return 1
}
if ( bTimesContacted > aTimesContacted ) {
return -1
}
return 0;
})
console.log(sorted)
setData(sorted)
}
Its because you are using the same ref of the array, you need set the new data with
setData(old => "sorted data");
to change the reference of the state and it updates
function filterByContactAmount():void {
let sorted = data.sort((a:any, b:any) => {
let aTimesContacted:number = a.data.person.contact.list.reduce((acc:number, val:any):number => acc + val.history.length, 0)
let bTimesContacted:number = b.data.person.contact.list.reduce((acc:number, val:any):number => acc + val.history.length, 0)
if ( aTimesContacted > bTimesContacted ) {
return 1
}
if ( bTimesContacted > aTimesContacted ) {
return -1
}
return 0;
})
console.log(sorted)
setData(old => [...sorted]) // Sorted is the new state sorted
}
You are mutating sate, the other answer is probably not the best because you are still mutating state and then setting state with a copy of the already mutated value.
The sort function can also be optimized. Maybe try the following:
function filterByContactAmount() {
let sorted = data
.map(d => ({//map shallow copies the array
...d,//shallow copies the item
sortedNum: d.data.person.contact.list.reduce(//do this once for every item, not for every time sort callback is called
(acc, val) => acc + val.history.length,
0
),
}))
.sort((a, b) => a.sortedNum - b.sortedNum);
console.log(sorted);
setData(sorted);
}
I think issue is located under d.claimable, I suppose it is boolean variable type. You must know that every key prop must be unique. Check if you have for example.id property, if not add it.
Uniqueness of key prop is very important during reconciliation process.
Unique identifier with a group of children to help React figure out which items have changed, have been added or removed from the list. It’s related to the “lists and keys” functionality of React described here.
Very nice article about reconciliation.
<tbody>
{ data.map(d => <TaskItem key={d.claimable} task={d}/>) }
</tbody>

react , using <br> inside .map inside for (object)

I know in react you can't use <html> like strings, i'm looking for a solution but i don't see clear, how in this complex logic you can to insert a <br/> object and not like string, because variable 'newvalue' need to store and conditionally add br to separate elements in rows.
let values = JSON.parse(value);
let newvalue = '';
values.map((item) => {
for (const key in listViewField.subfields) {
let nameField = key;
if (typeof item[nameField] !=='undefined') {
newvalue += item[nameField]+ ' ';
}
}
// if (newvalue.trim() !=='') newvalue += '<br/>'; // doesn't work
});
value = newvalue;
return <div key={keyIndex}>{value}</div>;
Rather than using map, you can just iterate over your values and push them into an array.
let values = JSON.parse(value);
let contents = [];
for (let value of values) {
contents.push(<span key={value.key}>{value.name}</span>);
if (value.condition) {
contents.push(<br key={`${value.key}-br`} />);
}
});
return <div>{contents}</div>;
Don't forget to add a unique key to each item.
To add different option, if you want to use it with map and your item is simple string, then you can do something like this.
items.map((item, key) => {
<span style={{whiteSpace: 'pre'}} key={key}>{item + '\n'}</span>
});
Note that I have added style={{whiteSpace: 'pre'}} and + \n

Cannot read property 'toJS' of undefined

I have two arrays. But when one of them is null it gives the following error:
Cannot read property 'toJS' of undefined in that line
Here's the relevant call that triggers the error: {groupsGuney.toJS()}
Here's my declaration of the variables let groupsGuney, groupsKuzey;
And finally here are my two arrays. But when one of them is null it gives the error:
...
if (muso == 1) {
groupsGuney = this.props.groups
.groupBy((group, idx) => idx % maxRows)
.map((ggs, idx) => {
return this.renderGroups(ggs, idx);
}).toList().flatten(true);
}
if (muso == 2) {
groupsKuzey = this.props.groups
.groupBy((group, idx) => idx % maxRows)
.map((ggs, idx) => {
return this.renderGroups(ggs, idx);
}).toList().flatten(true);
}
var result = (
<div>
<div className={classSelector + ' discard-mini-box-area'} >
{ groupsGuney.toJS() }
</div>
<div className={classSelector + ' discard-mini-box-area'} >
{ groupsKuzey.toJS() }
</div>
</div>
);
return result;
}
}
export default DiscardMiniBoxArea;
Instead of doing:
<div>
<div className={classSelector + ' discard-mini-box-area'} >
{groupsGuney.toJS()}
</div>
....
you should do:
<div>
<div className={classSelector + ' discard-mini-box-area'} >
{groupsGuney && groupsGuney.toJS()}
</div>
....
Before calling a function on your object, you need to make sure it's there. If you're uncertain about your object having the function at all times, you will need an additional check, that makes sure toJS is there and that it's a valid function.
If that's the case, update what's inside your container to:
{groupsGuney && typeof groupsGuney.toJS === 'function' && groupsGuney.toJS()}
However, ideally, you would not render at all this specific group if what you would like to render is not there. You should move these checks to before you render your component.
My motivation here is mostly that my call .get of undefined poops itself really hard, and initializing properly all over the place helps, but doesn't catch all edge cases. I just want the data or undefined without any breakage. Specific type checking causes me to do more work later if I want it to make changes.
This looser version solves many more edge cases(most if not all extend type Iterable which has .get, and all data is eventually gotten) than a specific type check does(which usually only saves you when you try to update on the wrong type etc).
/* getValid: Checks for valid ImmutableJS type Iterable
returns valid Iterable, valid Iterable child data, or undefined
Iterable.isIterable(maybeIterable) && maybeIterable.get(['data', key], Map()), becomes
getValid(maybeIterable, ['data', key], Map())
But wait! There's more! As a result:
getValid(maybeIterable) returns the maybeIterable or undefined
and we can still say getValid(maybeIterable, null, Map()) returns the maybeIterable or Map() */
export const getValid = (maybeIterable, path, getInstead) =>
Iterable.isIterable(maybeIterable) && path
? ((typeof path === 'object' && maybeIterable.getIn(path, getInstead)) || maybeIterable.get(path, getInstead))
: Iterable.isIterable(maybeIterable) && maybeIterable || getInstead;
//Here is an untested version that a friend requested. It is slightly easier to grok.
export const getValid = (maybeIterable, path, getInstead) => {
if(valid(maybeIterable)) { // Check if it is valid
if(path) { // Check if it has a key
if(typeof path === 'object') { // Check if it is an 'array'
return maybeIterable.getIn(path, getInstead) // Get your stuff
} else {
maybeIterable.get(path, getInstead) // Get your stuff
}
} else {
return maybeIterable || getInstead; // No key? just return the valid Iterable
}
} else {
return undefined; // Not valid, return undefined, perhaps should return false here
}
}
Just give me what I am asking for or tell me no. Don't explode. I believe underscore does something similar also.

Resources