Issues showing Firebase array object in react JS - javascript

I've been trying to work out whats going in the below code all day and after searching through stack over flow and you-tube, I still cant figure out whats wrong with my code.
Essentially I have a user document stored on firebase firestore. Inside the document I have an array of 'friends'.
[Heres what my firebase backend looks like.
I'm pulling the whole document into my React JS project and saving it onto a global state called friendsList.
Inside my profile page component.The console logs the user attribute of each user in the friend array (so its definitely getting the data), console log output
Now inside the return section on my profile page component, I go to pass in the friend as an object and display the FriendItemUI component, however nothing displays and no error occurs. I even tried to just display a paragraph <p and log just the user ID of the currently selected array item, but nothing showed in the console log, no errors, and nothing displayed on the component itself.
Now if i place the
console.log(friend.friend.user)
inside the return statement, it will log the users ID to the console without any issues.
So it seems to be that i can log the information to the console, inside and outside the return function, but i cant pass the actual array object into another component and i cant actually visiually show any information from the array in a div?
I'm not sure what im doing wrong and any help is appreciated.
Profile Page Component:
render(){
const { friendsList } = this.props;
return (
<div>
<a class="waves-effect waves-light btn" onClick={() => this.sendFriendRequest()}>Add
Friend</a>
{ friendsList && friendsList.map(friendsList => {
{ friendsList.friends.map(friend => {
//friendsList is a firebase firestore document.
// friendsList.friends is the array 'friends' inside the document
(async() => {
console.log("waiting for variable");
while(typeof friend.friend.user == "undefined")
await new Promise(resolve => setTimeout(resolve, 1000));
(console.log("variable is defined: " + friend.friend.user))
return(
<div>
<p>{friend.friend.user}</p>
<FriendItemUI friend={friend}/>
</div>
)
})();
})}
})}
</div>
)
}
FriendItemUI.JS component
class FriendItemUI extends Component {
render() {
const { friend } = this.props;
// this log doesnt even display anything to the console
console.log(friend + 'test')
if(friend) {
return <p> testing {friend.friend.user} | {friend.friend.personalMessage} | {friend.friend.friendStatus} </p>
}
else {
return(
<div>
<p>error loading friendlist</p>
</div>
)
}
}}
export default FriendItemUI
Now heres where it gets even more weird, If i dont want to go through all the friends in the array object and just want to display one friend, I can with the following code. [this code will now pass the friends array object into the FriendItemUI component and successfully display the name, personalMessage and friendStatus which is all taken from the firestore document array object..
const { friendsList } = this.props;
{ friendsList && friendsList.map(friendsList => {
return (
<FriendItemUI friend={friendsList.friends[1]}/>
)
I'm just really confused, I dont want to hard code which index values of the array i want to show, I'm trying to make the component check to see how many objects in the array there is, loop through the array and pass each of the friends from the array into the FriendItemUI component.

I'm super silly!, turns out that console.log and manually rendering a state was fine because react JS was smart enough to only render once the state values were defined with my firebase data.
All i had to do was modify the code as per below and add a check to only loop through the array if the array state value is != null. If the array state value is == null then return a loading results text message.
everything now works. Posting this answer incase someone runs into a similar issue.
render(){
console.log(this.props); // debug log
const { friendsList, profile } = this.props;
console.log('test log' + profile);
if(profile.friends!=null){
return(
<div className="container">
<a class="waves-effect waves-light btn" onClick={() => this.sendFriendRequest()}>Submit Friend Request</a>
{ profile && profile.friends.map(profile => { //if valid survey, return
return (
<div>
<FriendItemUI friend={profile}/>
</div>
)
})}
</div>
)
} else{
return (
<div>
<p style={{textAlign: 'center'}}>Loading Results</p>
</div>
)
}
}

Related

React DOM not re-rendering after state change of array property

I do understand that this problem is very common and most people might find this as a duplicate but I am at my wits end and that is why I am here.
I have a React component called App is a functional component.
Start of App component
function App() {
const [results, setResults] = useState([]);
const [isLoading, setIsLoading] = useState(false);
const [name, setName] = useState('');
const [isNameSelected, setIsNameSelected] = useState(false);
...
There is a child component which is acting erratically at the moment and it is part of the return components of the App function.
Section of Code in return statement of the App component:
<ListGroup className="typeahead-list-group">
{!isNameSelected &&
results.length > 0 &&
results.map((result: Result) => (
<ListGroupItem
key={result.cik}
className="typeahead-list-group-item"
onClick={() => onNameSelected(result)}
>
{result.cik + ' | ' + result.name}
</ListGroupItem>
))}
</ListGroup>
A change to to results is handled by the component FormControl's onChange function here also part of the return statement of App:
<FormControl
placeholder="Entity/CIK"
id="searchInput"
type="text"
autoComplete="off"
onChange={handleInputChange}
value={name}
/>
handleInputChange is defined in App function as:
const handleInputChange = (e: any) => { // Triggers when search bar is changed
// remove error bubble
setAlertMessage(new AlertData('', false));
const nameValue = e.target.value; // get the new input
setName(nameValue); // set the new input
// even if we've selected already an item from the list, we should reset it since it's been changed
setIsNameSelected(false);
setResults([]); // clean previous results
if (nameValue.length > 1) { // if the input is more than 1 character
setIsLoading(true); // set loading to true
updateSearchInput(nameValue) // get the results
.then((res) => {
setResults(res as React.SetStateAction<never[]>); // set the results
setIsLoading(false); // set loading to false
})
.catch((error) => {
// error bubble
let strError = error.message;
strError = strError.split(':').pop();
let errorMessage: AlertData = new AlertData(strError, true); // create error message for empty search
setAlertMessage(errorMessage); // set alert message
// loading spinner
setIsLoading(false);
});
}
}
However when there is an input change in form control, like typing in an entire word, the search functionality works, populating the DOM with suggested words. However when I clear the value in FormControl really fast (by pressing Backspace/Delete several times in quick succession), then the search results stay. Doing it slow or selecting and clearing it all at once however does not show this erratic behavior.
I have used console.log to print out the value of results in the an empty component like this:
{console.log(results) && (<div><div/>)}
in return statement of App to see what the contents of results are. However it does show that results value were not updated by setResults().
This problem however does not exist for the other states utilized here. Why?
EDIT
From the answer accepted below from #ghybs. This is a timeline of what might be happening with the call:
Enter search
await call runs but request response is slow so takes a while.
Delete all the keyword in search
results is made empty with setResults([]) in handleInputChange call.
await call finishes. setResults(res as React.SetStateAction<never[]>) runs making results non-empty.
You very probably just have plenty concurrent requests (1 per key stroke, including back space?), and unordered results from your updateSearchInput async function: the last received one overwrites your results, but that one may not originate from your last key stroke (the one that removed the last character from your textarea).
Typically if an empty search is faster than a search with plenty words, the results from empty input do clear your results, but then these are filled again by the results of a previous search.

Component is not rendering the output when navigating back from different component (ReactJS)

I am facing difficulties rendering array on screen when I navigate back to the component. I have been trying and searching since morning for the solution but no luck. Please let me explain
I have a react component called ShowTags.tsx that contains a callback function handleTagReading() which return strings every second (async behavior).
When the string tag arrives I am storing it in [storeTags] state array and in the return method, using map function to iterate and display the tags.
The Sample code
export default function ShowTags() {
//array to store string tags
const [storeTags, setStoreTags] = useState<String[]>([]);
//callback method
const handleTagReading = (tag) => {
console.log("print the tag" + tag) // this line runs everytime
setStoreTags(storeTags => [...storeTags!, tag]);
}
return (
<>
/*This component contains a button, on button click it runs the loop method which runs
the parent callback method*/
<ReadTags parentCallback = {handleTagReading} />
<div className="container">
{storeTags && storeTags!.map((item, index) => {
return (
<div>
{item}
</div>)
})}
</div>
</>
)
}
This code works perfectly as long as I am on The ShowTags component for the first time. The moment I navigate to a different component and come back, the map method shows nothing. But console.log() still runs showing the tags coming from a callback.
I have tried using the useEffect(), cleanup(), boolean states variables, non state variables but the component does not render when switching back to ShowTags component.
Please help me out here and let me know if you need more information
UPDATE -edit
For simplicity I said async behavior but actually I am using Serial USB API to read data from RFID reader (external hardware device connected via USB)
The ReadTags() component contains lot of code but I am sharing the necessary bits
export default function ReadTags(props) {
//send diffenent commands to reader on button press
async function sendSerialLine() {
try{
await writer.write(dataToSend);
}catch(e){
console.log("the write error")
setShowConnect(true)
}
}
//The listenToPort method runs continuously when the RFID reader gets connected via Serial USB and does not stop.
async function listenToPort(){
/*serial USB API implementation*/
textDecoder = new TextDecoderStream();
readableStreamClosed = port.readable.pipeTo(textDecoder.writable);
reader = textDecoder.readable.getReader();
while (true) {
const { value, done } = await reader.read();
if (done) {
reader.releaseLock();
break;
}
//parent callback
props.parentCallback(value);
}}
return (
<div>
<p onClick={() => sendSerialLine()} className="btn btn-primary">Start Reading</p>
</div>
)
}
Try to use useCallback with handleTagReading

Get a List buckets react components

I am trying to learn and produce something at the same time.
This is an example of code for a component that should read buckets and produce a list of them. On the paper the console.log is working, but I can get the code to save that list into a variable that I can use to produce the XHTML list.
class ListBuckets extends React.Component{
constructor(props){
super(props);
this.state = {
listBuckets: []
};
this.GetBuckets = this.GetBuckets.bind(this);
}
GetBuckets(){
let tmpCmp = this;
var BucketsApi = new ForgeSDK.BucketsApi(); //Buckets Client
// Get the buckets owned by an application.
// Use the oAuth2TwoLegged client object and the credentials object that were
// obtained from the previous step
// notice that you need do add a bucket:read scope for the getBuckets to work
BucketsApi.getBuckets({}, null, tmpCmp.props.credentials)
.then(function(buckets){
console.log(buckets.body.items);
}, function(err){
console.error(err);
});
}
render(){
return (
<div className="card" style={{width: '18rem'}, {margin: "10px"}}>
<div className="card-body">
<h5 className="card-title bg-dark text-white" style={{padding: "5px"}}><FaFolder/> Buckets</h5>
<h6 className="card-subtitle">List of buckets</h6>
<p className="card-text">
This card provides a list of available buckets
</p>
{
this.props.credentials!==null
? <ul>{this.GetBuckets()}</ul>
: <div>
<div className="spinner-border spinner-border-sm"></div>
<span> Waiting for credentials...</span>
</div>
}
</div>
</div>
)
}
}
Can ayone help me through this?
First off, you need to store or return buckets.body.items from the GetBuckets() function. You haven't done anything with them here, so they just get discarded after the function runs. Use setState() to store them in component state once you receive them:
GetBuckets(){
let tmpCmp = this;
var BucketsApi = new ForgeSDK.BucketsApi();
BucketsApi.getBuckets({}, null, tmpCmp.props.credentials)
.then(function(buckets){
console.log(buckets.body.items);
// The important thing happens here:
this.setState({ listBuckets: buckets.body.items });
}, function(err){
console.error(err);
});
}
Secondly, this function is going to get called every time the component re-renders, so you may only want to call it inside a one of the component lifecycle methods like componentDidMount(). Since it's an async function, you may have to do some null-checking in later in your render function.
Lastly, you can't just stick this array into your render function and expect it to return a an <li> for each element in the array. You need to map over the objects and return some JSX:
...
{
this.props.credentials!==null
? <ul>
{this.state.listBuckets.map(bucket =>
<li>bucket.data</li>)}</ul>}
: <div>
<div className="spinner-border spinner-border-sm"></div>
<span> Waiting for credentials...</span>
</div>
}
Adding to what #tobiasfried said:
Using Forge SDKs on the client side is not recommended as it could potentially expose the Forge application credentials. A better practice is to provide your own server endpoint which makes the Forge API calls and returns data in any format your client app needs. Take a look at the Learn Forge tutorial and its Node.js sample. The specific endpoint that lists all available buckets is here.

How to Loop Through Firebase JSON in React

I have the following JSON Data coming out of Firebase RTDB:
{"\"1\"":{"itemDescription":"This is a description of the item.","itemName":"Item Name","itemValue":{"costAmount":200,"costType":"dol"}},
"\"2\"":{"itemDescription":"This is an item description.","itemName":"Item Name 2","itemValue":{"costAmount":500,"costType":"dol"}}}
and so on...
The data is parsed (json.parse) and stored a variable called parsedJSONData in state.
I've tried looping through it using other recommendations on this site and others, such as:
this.state.parsedJSONData.map(json => {
<div className="item">
<p>Item: {json.itemName}</p>
</div>;
});
And I'm getting errors like the below:
Line 68: Expected an assignment or function call and instead saw an expression no-unused-expressions
Any tips on what I can do? I know the data is parsed correctly, because if I output the contents of the parsedJsonData to the console, I can see the data is structured correctly?
Try Using forEach() instead of map()
You are not returning anything inside of your map(). Here you can look at the different ways to return values from an arrow function, but in short, data will be returned in these two forms:
json => json.itemName
json => {return json.itemName;}
Drop the {} inside of your map or throw a return inside like so:
this.state.parsedJSONData.map(({itemName}) =>
<div className="item">
<p>Item: {itemName}</p>
</div>
);
or
this.state.parsedJSONData.map(({itemName}) => {
return <div className="item">
<p>Item: {itemName}</p>
</div>;
});
Try this. I have tried this and it works for the your json Data.
render() {
var tempData = [];
const theDataWeHave = this.state.parsedJSONData;
for(var row in theDataWeHave){
var rowEntry = theDataWeHave[row];
tempData.push(rowEntry);
}
var renderVariable = tempData.map(function(item,index) {
return ( <RenderThisComponent key={index} item={item}/> )
})
}
return(
//your code for other stuff
{renderVariable} //Place this where you want to render the component
)
The component you want to render will be a separate functional component with props passed to it.
You can write in the same file or you can write in separate file and import+export
.I have done in the same file so it do not need to be exported or imported.
const RenderThisComponent = (props) => (
<div className="item">
<p>Item: {props.item.itemName}</p>
</div>
)

RxJS and React multiple clicked elements to form single data array

So I just started trying to learn rxjs and decided that I would implement it on a UI that I'm currently working on with React (I have time to do so, so I went for it). However, I'm still having a hard time wrapping my head around how it actually works... Not only "basic" stuff like when to actually use a Subject and when to use an Observable, or when to just use React's local state instead, but also how to chain methods and so on. That's all too broad though, so here's the specific problem I have.
Say I have a UI where there's a list of filters (buttons) that are all clickeable. Any time I click on one of them I want to, first of all, make sure that the actions that follow will debounce (as to avoid making network requests too soon and too often), then I want to make sure that if it's clicked (active), it will get pushed into an array and if it gets clicked again, it will leave the array. Now, this array should ultimately include all of the buttons (filters) that are currently clicked or selected.
Then, when the debounce time is done, I want to be able to use that array and send it via Ajax to my server and do some stuff with it.
import React, { Component } from 'react';
import * as Rx from 'rx';
export default class CategoryFilter extends Component {
constructor(props) {
super(props);
this.state = {
arr: []
}
this.click = new Rx.Subject();
this.click
.debounce(1000)
// .do(x => this.setState({
// arr: this.state.arr.push(x)
// }))
.subscribe(
click => this.search(click),
e => console.log(`error ---> ${e}`),
() => console.log('completed')
);
}
search(id) {
console.log('search --> ', id);
// this.props.onSearch({ search });
}
clickHandler(e) {
this.click.onNext(e.target.dataset.id);
}
render() {
return (
<section>
<ul>
{this.props.categoriesChildren.map(category => {
return (
<li
key={category._id}
data-id={category._id}
onClick={this.clickHandler.bind(this)}
>
{category.nombre}
</li>
);
})}
</ul>
</section>
);
}
}
I could easily go about this without RxJS and just check the array myself and use a small debounce and what not, but I chose to go this way because I actually want to try to understand it and then be able to use it on bigger scenarios. However, I must admit I'm way lost about the best approach. There are so many methods and different things involved with this (both the pattern and the library) and I'm just kind of stuck here.
Anyways, any and all help (as well as general comments about how to improve this code) are welcome. Thanks in advance!
---------------------------------UPDATE---------------------------------
I have implemented a part of Mark's suggestion into my code, but this still presents two problems:
1- I'm still not sure as to how to filter the results so that the array will only hold IDs for the buttons that are clicked (and active). So, in other words, these would be the actions:
Click a button once -> have its ID go into array
Click same button again (it could be immediately after the first
click or at any other time) -> remove its ID from array.
This has to work in order to actually send the array with the correct filters via ajax. Now, I'm not even sure that this is a possible operation with RxJS, but one can dream... (Also, I'm willing to bet that it is).
2- Perhaps this is an even bigger issue: how can I actually maintain this array while I'm on this view. I'm guessing I could use React's local state for this, just don't know how to do it with RxJS. Because as it currently is, the buffer returns only the button/s that has/have been clicked before the debounce time is over, which means that it "creates" a new array each time. This is clearly not the right behavior. It should always point to an existing array and filter and work with it.
Here's the current code:
import React, { Component } from 'react';
import * as Rx from 'rx';
export default class CategoryFilter extends Component {
constructor(props) {
super(props);
this.state = {
arr: []
}
this.click = new Rx.Subject();
this.click
.buffer(this.click.debounce(2000))
.subscribe(
click => console.log('click', click),
e => console.log(`error ---> ${e}`),
() => console.log('completed')
);
}
search(id) {
console.log('search --> ', id);
// this.props.onSearch({ search });
}
clickHandler(e) {
this.click.onNext(e.target.dataset.id);
}
render() {
return (
<section>
<ul>
{this.props.categoriesChildren.map(category => {
return (
<li
key={category._id}
data-id={category._id}
onClick={this.clickHandler.bind(this)}
>
{category.nombre}
</li>
);
})}
</ul>
</section>
);
}
}
Thanks, all, again!
Make your filter items an Observable streams of click events using Rx.Observable.fromevent (see https://github.com/Reactive-Extensions/RxJS/blob/master/doc/gettingstarted/events.md#converting-a-dom-event-to-a-rxjs-observable-sequence) - it understands a multi-element selector for the click handling.
You want to keep receiving click events until a debounce has been hit (user has enabled/disabled all filters she wants to use). You can use the Buffer operator for this with a closingSelector which needs to emit a value when to close the buffer and emit the buffered values.
But leaves the issue how to know the current actual state.
UPDATE
It seems to be far easier to use the .scan operator to create your filterState array and debounce these.
const sources = document.querySelectorAll('input[type=checkbox]');
const clicksStream = Rx.Observable.fromEvent(sources, 'click')
.map(evt => ({
name: evt.target.name,
enabled: evt.target.checked
}));
const filterStatesStream = clicksStream.scan((acc, curr) => {
acc[curr.name] = curr.enabled;
return acc
}, {})
.debounce(5 * 1000)
filterStatesStream.subscribe(currentFilterState => console.log('time to do something with the current filter state: ', currentFilterState);
(https://jsfiddle.net/crunchie84/n1x06016/6/)
Actually, your problem is about RxJS, not React itself. So it is easy. Suppose you have two function:
const removeTag = tagName =>
tags => {
const index = tags.indexOf(index)
if (index !== -1)
return tags
else
return tags.splice(index, 1, 0)
}
const addTag = tagName =>
tags => {
const index = tags.indexOf(index)
if (index !== -1)
return tags.push(tagName)
else
return tags
}
Then you can either using scan:
const modifyTags$ = new Subject()
modifyTags$.pipe(
scan((tags, action) => action(tags), [])
).subscribe(tags => sendRequest(tags))
modifyTags$.next(addTag('a'))
modifyTags$.next(addTag('b'))
modifyTags$.next(removeTag('a'))
Or having a separate object for tags:
const tags$ = new BehaviorSubject([])
const modifyTags$ = new Subject()
tags$.pipe(
switchMap(
tags => modifyTags$.pipe(
map(action => action(tags))
)
)
).subscribe(tags$)
tags$.subscribe(tags => sendRequest(tags))

Categories

Resources