Multiple React Components Getting Same Props - javascript

I'm trying to build a super basic UI for visualizing a live data stream using React and Javascript. I'm very new to both Javascript and React so I'm sure the issue is something basic that I am overlooking.
I want to reuse the same React component on the main page. I have a custom scatter plot chart built using the react-chartjs2 library. When I was only displaying one instance of the chart everything worked perfectly but now that there are two charts they seem to be two instances of the second instantiation and I can't figure out why.
RadarScatter.js Code:
export function RadarScatter(props) {
const [count, setCount] = useState(0);
const [radarData, setRadarData] = useState(defaultData);
async function updateData() {
let dataServerAddr = "http://127.0.0.1:5000/foghorn.api";
let cur_time_ms = Date.now();
let fetch_str = dataServerAddr + "/RADAR/" + props.ID + "/" + String(cur_time_ms);
console.log(fetch_str);
const response = await fetch(fetch_str);
//FIXME Handle bad response
const blob = await response.json();
let graphData = {
datasets: [
{
label: "Radar" + props.ID,
data: blob.data.pc.map((el) => ({x: -el.range*Math.sin(el.azimuth), y: el.range*Math.cos(el.azimuth)})),
backgroundColor: 'rgba(255, 99, 99, 1)',
},
],
};
setRadarData(graphData);
}
useEffect(() => {
const updateTimer = setInterval(()=>{
setCount((oldVal)=>{
updateData();
return (oldVal+1);
});
}, 250);
return () => {
clearInterval(updateTimer);
}
}, []);
options.plugins.title.text = "Radar " + props.ID
return (
<div>
<Scatter options={options} data={radarData} />
</div>
);
}
App.js Code:
function App() {
return (
<div className="App">
<h1>Top Down Radar FOV</h1>
<RadarScatter ID="100:101:102:103:104:105"/>
<RadarScatter ID="200:201:202:203:204:205"/>
</div>
);
}
I've read both of these stackoverflow posts.
Reusing multiple instances of react component with different props
React multiple instances of same component are getting same state
The solution in both of these questions was to add some unique state to each instance so that they can be identified by the parent. Don't I already have that with my ID value being different for each instances of <RadarScatter/>?
When viewing the console output it appears as though the component function is being called 4 times total. The first two times are with the first ID value and the second two times are with the second ID value. This doesn't make any sense to me. Why is the function not called once with each set of props?
I've also tried using the <Fragments></Fragments> container from another link that I read but that didn't seem to have any impact either.
If anyone can explain not just how to fix my duplicate UI issue but also what is happening to cause it I would really appreciate it.

Related

Unable to render component when using Map as state type

I am fairly new to React. I was trying to create below component
const MyComponent = () => {
const [data, setData] = useState<Map<string, MyData>>(new Map<string, MyData>())
useEffect(() => {
const parsedData: Map<string, MyData>> =
JSON.parse(mockedData)
setData(parsedData)
}, [])
return <>{data.size > 0 ? getDataComponents() : <></>}</>
}
As part of this, I am using mockedData to replicate the data I would receive from an API. This compiles correctly.
However at runtime, when the render cycle is triggered post effect, data.size is shown as undefined as data type is object instead of Map, due to which getDataComponents() is never triggered. I can also see the values on the object but not entirely certain what is causing it to be not identified as Map.

Why does my React app not re-render or display changes on the DOM except I relaod?

import { useEffect, useState } from "react";
function Popular() {
const [popular, setPopular] = useState([]);
useEffect(() => {
getPopular();
}, []);
const getPopular = async () => {
const api = await fetch(
`https://api.spoonacular.com/recipes/random?apiKey=${process.env.REACT_APP_RECIPE_API_KEY}&number=9`
);
const data = await api.json();
setPopular(data.recipes);
};
return (
<div>
{popular.map((recipe) => {
return (
<div>
<p>{recipe.title}</p>
</div>
);
})}
</div>
);
}
export default Popular;
I am pretty new to React, and I encountered this issue which I have been trying to fix to no avail. The code is a component that is to return a list of recipe title to my app. I am fetching data from an API in the getPopular() function which is set to the setPopular function variable of the useState() method. But when I save my work and return to the browser, the changes does not display. The list does not display, but if I console.log(data.recipes) it displays on the console.
Before now, if I made any change (maybe a text change) the React app renders it without reloading, but now I have to reload the page before I see the change.
Please how do I fix this issue? So that I can see changes without having to reload the page manually.
Not saying that this is the problem, but getPopular() should not be called after its declaration? By this I mean:
const getPopular = async () => {
const api = await fetch(
/...
};
useEffect(() => {
getPopular();
}, []);
Another thing that bugs me is, although JS/React is case sensitive, I really think you should avoid having a const called popular, since your functions is Popular.
Please, let me know if the order did matter for your problem. I will review some react classes soon, if i get another inside, i'll let you know.

Nextjs onclick data to div

I am using a table to display data. I would like to get the row data into a div when the user clicks the table row:
Currently I am getting the data onclick which is working:
const handleClick = (rowData) => {
console.log(rowData);
}
Although currently not sure how I should pass and change the data into the div, here's the full code:
const Start = ({rowData}) => {
const options = {
onRowClick: rowData => handleClick(rowData),
}; // MUI Datatables onRowClick, works fine
const handleClick = (rowData) => {
console.log(rowData); // shows data in console, works
}
return (
<div className="tablehead">
<h2>({rowData})</h2> // no data here, how to pass the onclick data here?
</div>
)
}
export default Start;
So what's happened here is that you have mixed up passing row data props with a variable that is the same name. Instead you need to look into managing state through hooks.
const Start = ({rowData}) => {
const [dataForDisplay, setDataForDisplay] = useState(rowData);
const handleClick = (rowData) => {
setDataForDisplay(rowData);
}
return (
<div className="tablehead">
<h2>({dataForDisplay})</h2>
</div>
)
}
export default Start;
However using props to seed state is most of the time an anti-pattern, so you might want to reconsider your component hierarchy. Either way however you'll need to look into state management and hooks.
https://reactjs.org/docs/state-and-lifecycle.html

Synchronising Event Handler for Search in React

I have been learning js and then React.js over the last few weeks, following tutorials on Codecademy and then Educative.io (to learn with the new hooks, rather than the class-based approach). In an attempt to apply what I have learned I have been messing around creating a number of common website features as React components on a hello-world project.
Most recently I have been trying to make a search component, which uses the Spotify API to search for a track, but have been running into synchronisation issues which I can't quite figure out how to solve using the js synchronisation tools that I know of. I come from a Java background so am more familiar with mutexes/semaphores/reader-writer locks/monitors so it may be that I am missing something obvious. I have been basing the code on this blog post.
In my implementation, I currently have a SongSearch component, which is passed its initial search text as a property, as well as a callback function which is called when the input value is changed. It also contains searchText as state, which is used to change the value of the input.
import * as React from 'react';
interface Props {
initialSearchText: string,
onSearchTextUpdated: (newSearchText: string) => void;
}
export const SongSearch = (props: Props) => {
const [searchText, setSearchText] = React.useState(props.initialSearchText);
const onChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const newSearchText = e.target.value;
setSearchText(newSearchText);
props.onSearchTextUpdated(newSearchText);
}
return <input value={searchText} onChange={onChange}/>;
};
The results are currently just displayed a list in the SearchResults component, the values of which are passed as an array of songs.
import * as React from 'react';
import { SongInfo } from './index';
interface Props {
songs: SongInfo[]
}
export const SearchResults = (props: Props) => {
return (
<ul>
{props.songs.map((song) => {
return <li key={song.uri}>{song.name}</li>
})}
</ul>
);
}
In the App component, I pass a callback function which sets the state attribute searchText to the new value. This then triggers the effect which calls updateSongs(). If we have an auth token, and the search text isn't empty we return the results of the API call, otherwise we return an empty list of songs. The result is used to update the tracks attribute of the state using setTracks().
I have cutdown the code in App.tsx to only the relevant parts:
import SpotifyWebApi from 'spotify-web-api-js';
import React from "react";
// ... (removed irrelevant code)
async function updateSongs(searchText: string): Promise<SongInfo[]>{
if (spotify.getAccessToken()) {
if (searchText === '') {
console.log('Empty search text.');
return [];
} else {
// if access token has been set
const res = await spotify.searchTracks(searchText, {limit: 10});
const tracks = res.tracks.items.map((trackInfo) => {
return {name: trackInfo.name, uri: trackInfo.uri};
});
console.log(tracks);
return tracks;
}
} else {
console.log('Not sending as access token has not yet');
return [];
}
}
function App() {
// ... (removed irrelevant code)
const initialSearchText = 'Search...';
const [tracks, setTracks] = React.useState([] as SongInfo[]);
const [searchText, setSearchText] = React.useState(initialSearchText);
React.useEffect(() => {
updateSongs(searchText)
.then((newSongs) => setTracks(newSongs))
}, [searchText]);
const content = <SearchResults songs={tracks}/>;
return (
<ThemeProvider theme={theme}>
<div style={{ minHeight: '100vh', display: 'flex', flexDirection: 'column' }}>
<Root config={mui_config}>
<Header
renderMenuIcon={(open: boolean) => (open ? <ChevronLeft /> : <MenuRounded />)}
>
<SongSearch initialSearchText={initialSearchText} onSearchTextUpdated={(newSearchText) => {
console.log(`New Search Text: ${newSearchText}`)
setSearchText(newSearchText);
}}/>
</Header>
<Nav
renderIcon={(collapsed: boolean)=>
collapsed ? <ChevronRight /> : <ChevronLeft />
}
classes={drawerStyles}
>
Nav
</Nav>
<StickyFooter contentBody={content} footerHeight={100} footer={footerContent}/>
</Root>
</div>
</ThemeProvider>
);
}
export default App;
The issue that I am having is that when I type in the name of a long song and then hold down backspace sometimes songs remain displayed in the list even when the search text is empty. From inspection of the console logs in the code I can see that the issue arises because the setTracks() is sometimes called out of order, in particular when deleting 'abcdef' quickly setTracks() the result of updateTracks('a') will be called after the result of updateTracks(''). This makes sense as '' does not require any network traffic, but I have spent hours trying to work out how I can synchronise this in javascript with no avail.
Any help on the matter would be greatly appreciated!
In your case the results are coming back differently because you send multiple events, and the ones that come first - fire a response and then you display it.
My solution would be to use a debounce function on the onChange event of the input field. So that the user will first finish typing and then it should start the search. Although there still might be some problems, if one search has started and the user started typing something else then the first one has finished and the second one has started and finished. In this you might find that cancelling a request helpful. Unfortunately you can't cancel a Promise, so you would have to read about RxJS.
Here's a working example using debounce
P.S.
You might find this conference talk helpful to understand how the event loop is working in JS.

TypeError: Cannot destructure property 'jobArray' of 'react__WEBPACK_IMPORTED_MODULE_0__.state' as it is undefined

I've been working on a job site for a few weeks. I am still pretty new to React. Every page is working perfectly (signin, signup etc) except this one for displaying jobs. The error I get when compiling is the title of this question. Also, the console logs aren't working at all.
I've tried everything like adding hard-coded data as you can see, but nothing works. The back-end code is working fine.
const token = getCookie('token');
const jobArray = ["dalas nalgas"];
class JobListClass extends React.Component {
state = { jobArray };
componentDidMount() {
!isAuth()? navigator.goTo("/") : this.load();
}
load = async () => {
try {
const response = axios({
method: 'GET',
url: process.env.REACT_APP_API+'/jobs'
})
console.log("response data:"+response.data);
this.setValues({jobArray : response.data});
//console.log("jobarray with data:" +jobArray);
}
catch(err) {
toast.error("No info has been retrieved.");
}
};
listJobs = () => {
const {jobArray} = state;
console.log("array:"+jobArray);
const array = jobArray.map((job, index) => {
return (
<div key={index}>
<h5 className="card-title">{ job.enterprise_name }</h5>
<p className="card-text">{ job.pos }</p>
<p className="card-text">{ job.desc }</p>
<p className="card-text">{ job.salary } USD per week.</p>
<p className="card-text">{ job.hours } per day.</p>
{ isAuth() && isAuth().role === 'admin' ?
<div>
<Link to={"jobs/update/" + job._id} className="btn btn-primary">Update</Link>
<button onClick={ this.showDetails }>Show</button>
</div>
: null }
</div>
)
});
return array;
};
I'll make easy for you to make this work.
First is why did you define jobArray as const on the top. Const is const. Their values cannot change once they define. And in the middle of code also you make state variable as the same const. So how react is going to find which one is it should destructure.
Since React is the library I cannot say do this in this and this way. But you have to follow best practices. Rather than keep the state in classes it's good to keep it inside constructor like this. If you are new to programming do a search on what is the constructor and why we're using it.
Again back to answer you can do this,
constrcutor(){
super();
this.state={
jobArray:[]
}
}
There after you have to bind data retrieving method inside constructor.
So then you can destructure jobArray as you did const {jobArray} = state;
When you are generating JSX template inside listJobs() method you didn't handle situations where handling empty array. To do that you can replace like this,
const array = jobArray|| jobArray.map((job, index) => {
And I couldn't see where are you rendering your all JSX templates and how it's going to return. So look into that as well. Normally if it is a class-based UI component it should have to render() method and inside its return where you return JSX. Complete that part and make sure to export your class component. So you can use it without any problems.

Categories