Ionic React component not updating/rendering image - html

I am using Ionic React to develop a web app. Since I am new to this I am running into a few issues here and there. One of which I haven't been able to fix.
I have a TeamLogo component that is supposed to display an image after fetching it from my rest api. However the view / image doesn't seem to update properly every time. So in some cases I just get the default logo. If I route/switch to a different page and then back it will render the correct logo. As soon as I reload the page it is back to the default logo. What am I doing wrong? I have tried without a default logo, then it's just blank and nothing shows even in html. So the default logo is just for visualization/debugging.
I have had this issue in the past but I was able to fix it by wrapping the TeamLogo inside a <React.Fragment>. However this time it won't work and I really have no clue why it even worked in the past. Why isn't it rerendering?
My code looks like this:
export const TeamLogo: React.FC<TeamLogoProps> = ({teamId, width}) => {
const [teamData, setTeamData] = useState<any>(null);
console.log('trying data from ' + teamId);
useEffect(() => {
if (!teamData || teamData.id != teamId) {
fetchTeam(teamId, (data) => {
setTeamData(data);
console.log('fetched data from ' + data.name + " " + teamId);
});
} else {
console.log('Had existing data for ' + teamData.name+ " " + teamId)
}
});
if(teamData != null) {
return (
<img className={teamData.name} width={width + "px"} src={"data:image/png;base64," + teamData?.baseLogo}/>
);
} else {
return <img className="NRG Esports" src="data:image/png;base64,defaultBase64..." width="50px"/>
}
};
And I am using it like this:
function getScoreTeamNameEntry(showScore: boolean, team: any) {
return (
<div className={(showScore ? "" : "hidden-keep-size ") + "name-entry"}>
{team.id ? (<Link className="float-right" to={"/team/" + team.id}>
<React.Fragment><TeamLogo width={50} teamId={team.id}/></React.Fragment>
<IonLabel className={"color-contrast"}>{team.name}</IonLabel>
</Link>) : <IonLabel className={"color-contrast"}>{team.name}</IonLabel>}
</div>);
}
Any help is appreciated!
EDIT:
I have played around with it a bit and was able to bypass the issue, but it's still very strange. Here is what I found out:
<div className="participant winner">
{
getPlaceholderEntry(teamData, id)
}
</div>
I am now passing the id instead of the teamData only so the logo can load from the sole team id and does not have to wait for the teamData to load.
Now getPlaceholderEntry just does this:
function getPlaceholderEntry(team: any, teamId: any) {
console.log("Placeholder");
return (
<React.Fragment>
{
getScoreTeamNameEntry(true, team, teamId)
}
</React.Fragment>)
}
However replacing the getPlaceholderEntry call with a direct
<React.Fragment>
{
getScoreTeamNameEntry(true, team, teamId)
}
</React.Fragment>)
for some reason does not render/update it yet again.
Also adding any sort of condition like
<div className="participant winner">
{
teamData && getPlaceholderEntry(teamData, id)
}
</div>
breaks it yet again. Any idea what's going on?

useEffect(() => {
... original code
},[teamData,teamData.id]);
you need to add the dependencies on the useEffect so the component will rerender when the data changes
https://reactjs.org/docs/hooks-reference.html#conditionally-firing-an-effect

Related

React + CSS Grid + MongoDB?

Im creating a webpage in which users can post stuff to a "bulletin board". These posts are gonna be styled as boxes, and I want to add them to a grid layout. This is my code:
import React from 'react';
//import Gallery from "./components/gallery.component"
function App() {
state = {
posts = [];
};
componentDidMount = () => {
this.getPosts();
};
getPosts = () => {
axios.get('(server)')
.then((response) => {
const data = response.data;
this.setState({ posts: data });
})
}
displayBlogPost = (posts) => {
if (!posts.length) return null;
return posts.map((post, index) => (
<div key={index} className="post__display">
<h3>"name " + this.state.name </h3>
</div>
));
};
render() {
return (
<div className="App">
<header className="container">
Logo
</header>
</div>
<div className="gallery">
{this.displayBlogPost(this.state.posts)} </div>
)
}
}
export default App;
Im a little unsure of how to work on the grid. Ive been looking into the CSS Grid documentation and I was wondering if it would be enough to keep my code as is and create a css file with an id #gallery that sets the attributes of the grid (display, grid-gap, repeat, etc). Would this be enough, or will I have to change my displayBlogPost function. I'm worried that the approach will consider gallery just containing one element and therefore there wont even be a grid.
Would appreciate some clarification and suggestions, I tried to find examples online, but none seemed to use react, mongodb, and css grid.
Its also hard because Im a newbie to React and MongoDB and JavaScript in general, but this project is for a hackathon and Im on a time crunch
Even though the post contains only one element the HTML DOM would show the grid in itself.
Yes, you can create a separate CSS file and include the req. CSS there.
Also, "name " + this.state.name you have not defined the state "name", how are you expecting for a display of value there?

Angular 6 - How to stop infinite polling in subscribe()

So I want to show an icon based on whether or not the number of projects in my list is > 3. I am using this getProjects() function that I need to subscribe to in order to get the data. I am setting a boolean when I subscribe that checks the number of projects in the list, then in my HTML, I use a ngIf to show the icon based on the boolean. I am able to get it to show correctly, however, I think I am constantly polling in my subscribe, and setting this boolean over and over again because it is making my webpage run really slow.
I have already tried the take(1) method which doesnt seem to stop the subscription, as well as set it to a "this.variable" scope inside my component. I am currently using event emitters however that is not working either.
This is my code so far,
Function that I subscribe to (in a different component):
getProjects(): Observable<ProjectInterfaceWithId[]> {
const organizationId = localStorage.getItem('organizationId');
return this.firestoreService.collection('organizations').doc(organizationId)
.collection('projects').snapshotChanges()
.pipe(
map(actions => actions.map(a => {
const data = a.payload.doc.data() as ProjectInterface;
const id = a.payload.doc.id;
return {id, ...data} as ProjectInterfaceWithId;
})),
map(list => {
if (list.length !== 0) {
this.buildProjectLookup(list);
this.projects = list;
return list;
}
})
);
}
Function that i use to get the data and set the boolean:
#Input() toggle: boolean;
#Output() iconStatus = new EventEmitter();
displayIcon() {
this.projectService.getProjects()
.pipe(take(1))
.subscribe(
list => {
if(list.length >= 3){
this.toggle = true;
this.iconStatus.emit(this.toggle);
}
});
}
HTML:
<i *ngIf="displayIcon()" class="material-icons">list</i>
Is there any way for me to literally just check the list length once so I don't get caught in this subscription loop? Thank you in advance!
It looks like it could be happening due to the ngIf referring to the displayIcon() method.
Every time change detection runs within your component, this method will be called. If your component is using default change detection, this will be very often.
see https://blog.angular-university.io/how-does-angular-2-change-detection-really-work/ for more
One way this could be fixed is by making the ngIf refer to a variable instead.
For example, you could set a projects$ observable using
this.projects$ = this.projectService.getProjects()
.pipe(
take(1),
tap(projects => this.iconStatus.emit(projects.length >= 3))
);
This observable should likely be instantiated in your ngOnInit() method.
Then in your template you can use
<i *ngIf="(projects$ | async)?.length >= 3" class="material-icons">list</i>

React Router v4 - How to detect back button navigation vs url refresh?

I have a url at /page (PAGE A) where I want to detect if the page was navigated to with history back from (PAGE B) or if the user was on (PAGE A) and manually refreshed the page from the URL bar refresh button (without using history back).
I looked into all the history, location, props by react router but didn't find a way to differentiate how the user navigated to the page.
In both scenarios, the history.action == 'POP' is the history action. Ideally it would be 'POP' when using the back button in the app to go back from page b to page a, and when on page a, when refreshing the page, it would be something other than 'POP' like 'REFRESH' for example.
How can we differentiate between both of them to run different logic in our app, since both trigger 'POP'?
Instead of comparing the history key, you can compare the pathname, for example, if you are in the page "/page1/page2" and hit refresh, the new location is the same. But if you hit the back action, the new location will be "/page1/".
This solution also uses a listener to listen to any action coming from history.
componentDidMount() {
const unlisten = history.listen((location, action) => {
if (action == 'POP') {
\\ thisLocation is the current location of your page
if (location.pathname != '/thisLocation/') {
alert('Back Pressed: ' + String(location.pathname));
} else {
alert('Refreshed: ' + String(location.pathname));
}
}
});
this.setState({ ...this.state, unlisten: unlisten });
}
componentWillUnmount() {
this.state.unlisten();
}
You can see more details in the link provided by Rei Dien as a comment of your question: https://www.npmjs.com/package/history
[EDIT]
Another way to do this is using https://www.npmjs.com/package/react-router-last-location and doing this:
import { useLastLocation } from 'react-router-last-location';
componentDidMount() {
const unlisten = history.listen((location, action) => {
const lastLocation = useLastLocation();
if (location.pathname == lastLocation.pathname) {
alert('Back Pressed: ' + String(location.pathname));
}
}
});
this.setState({ ...this.state, unlisten: unlisten });
}
componentWillUnmount() {
this.state.unlisten();
}
The downside is that there is no difference between activating the back action or clicking in a link that goes to the page that you was before, both would be detected as pressing back. If you don't want a new dependency, you can do it manually as stated in https://github.com/ReactTraining/react-router/issues/1066#issuecomment-412907443 creating a middleware.
I think this will at least point your in the right direction. Navigate to yourwebsite.com.
let current_page = history.state.key
if(history.action == 'POP') {
if(history.state.key == current_page) {
return 'page was refreshed'
}
return 'back button was pressed'
}

NetSuite Online HTML Form - link to list within NetSuite

I have an online html form that uses a select field to choose a record from an existing record list (postcodes specifically) which then auto-populates other fields on the form with the rest of the address. This all works fine except that the dropdown list on the form only goes up so far. I need to know if this a limit on the html dropdown or how many records can be passed to the list in the first place?
Also, ideally, it would be great if there was a way to do it via auto-complete so as you start typing the postcode, it only shows those beginning with said characters - is this possible?
I would set up a custom html template for your online form. You can then hide your standard field and include a custom text input field. Attach an event handler to the custom field to do the lookup via a suitelet. If the auto-population is already working then your event handler can update the hidden standard field once a match is made so Netsuite's built-in sourcing works.
A sample suitelet. The get... functions return anything that can be JSON used by your page.
function service(request, response) {
var obj = {
success: true
};
try {
var step = request.getParameter('step') || 'start';
switch (step) {
case 'start':
obj.choices = getChoiceRoots(request.getParameter('itemid'), request.getParameter('treeName'));
break;
case 'other':
obj.choices = getChoiceChildren(request.getParameterValues('choiceIds[]') || request.getParameterValues('choiceIds'));
break;
default:
throw "Unexpected step: " + step;
}
} catch (e) {
obj.success = false;
obj.message = e.message || e.toString();
nlapiLogExecution('ERROR', "error getting choices", (e.message || e.toString()) + "<br> " + request.getURL() + (e.getStackTrace ? "<br> " + e.getStackTrace().join("<br> ") : ''));
}
_sendJS(request, response, obj);
function _sendJS(request, response, respObject) {
response.setContentType('JAVASCRIPT'); //'application/json');
var callbackFcn = request.getParameter("jsoncallback") || request.getParameter('callback');
if (callbackFcn) {
response.writeLine(callbackFcn + "(" + JSON.stringify(respObject) + ");");
} else response.writeLine(JSON.stringify(respObject));
}
}
Then on your page you'd have a script that uses the suitelet. (the suitelet needs to be available without login and the audience needs to be All.
The code on your custom template or associated script file would look something like:
//suiteletURL will look like https://forms.netsuite.com. RegExp makes that root relative to the domain you are on.
//This is not actually necessary when using JSONP but it can be useful in some circumstances so I left it in this example.
$.getJSON(suiteletURL.replace(new RegExp(".*://[^/]+/"), '/')+"&"+$.param(params) +"&callback=?",
function(d, txtStatus,xhr){
if(d.errorCode){
if(fail){
fail(xhr, txtStatus, null);
}else{
showMessage("#appMessage", d.errorMessage || ("Request Failed with code: "+xhr.responseJSON.errorCode));
}
}else{
success(d, txtStatus, xhr); // your custom success handler. d is already an object; no parsing necessary.
}
});

Populating dropdown list with JSON data using 2 views/documents

I'm having some trouble populating a dropdownlist with some JSON data, i suspect that the error occurs because of the way im appending the $.post within the #stuff div, but i've tried this a couple of ways and just wont get the hang of it.
The select id="" tag & the div lies within another view (it's not part of this particular document) , is that a problem for populating the dropdown-list this way?
Ive tried to alert out the "listItems" and i've got the option values etc... dont get it why it wont populate.
Any help would be appreciated.
Json-response from the $.post =
{"childrendata":[{"id":"42","parent":"1","fName":"hej","lName":"nisse","birthdate":"2011-10-21"}]}
The jQuery/js:
$("#stuff").append(function(){
$.post("show_relations", {},
function(data)
{
$("#stuff").empty();
json = eval('('+data+')');
if(data == '{"childrendata":[]}')
{
$("#stuff").append("No relations registered.");
}
else
{
var listItems= "";
for (var i = 0; i < json.childrendata.length; i++)
{
listItems+= "<option value='" + json.childrendata[i].fName + "'>" + json.childrendata[i].lName + "</option>";
}
$("#child_list").html(listItems);
}
});
});
});
Edit: Based on your comment, I'll assume your problem is purely single-page.
The problem with that code would appear to be the fact that you're trying to use .append() with a function (which is valid jQuery), but that the function doesn't return anything that jQuery can append to the 'stuff' node; $.post makes an Ajax call, which returns immediately.
Instead, try something like the following (modifying the URL to the Ajax call as required):
$.post("url/to/post/to", {},
function(data) {
$("#stuff").empty(); //Clear your stuff div
var children = data.childrendata; //jQuery automatically unserializes json
if(children.length == 0) {
$("#stuff").append("No relations registered.");
}
else {
$('#stuff').append('<select id="child_list"></select>');
$.each(children,
function(index, value) {
//Append each option to the selectbox
$("#child_list").append("<option value='" + value.fName + "'>" + value.lName + "</option>");
}
);
}
},
'json'
);
$.each() is the generic jQuery iterator, which helps de-clutter the code.
What this does is make an Ajax post to the provided URL, which responds with the serialized json object. The callback takes that response (which jQuery has already unserialized by itself), adds a new select to the '#stuff' div, and then adds the dynamically-created options to the new select.
Endnote: My apologies for not posting the link to the $.each documentation, StackOverflow only allows me to post 2 hyperlinks in a single post currently.