Pass an array when it changes from parent to child - reactjs

I'm trying to get with nearbySearch from Google Maps API the places that are near to me and send this data to my child component for put markers in map.
This is the function where I call Google Service Nearby Search and it prints the correct data in the console.log
const handleZoomChanged = () => {
let lat = map.mapUrl.split('=')[1].split('&')[0].split(',')[0];
let lng = map.mapUrl.split('=')[1].split('&')[0].split(',')[1];
let places = getNearby(lat, lng);
console.log('places', places);
console.log('length', places.length); //EDIT: I tried to get the length of the array and get 0
return (
<MainMarkersGoogle places={places} />
)
}
But the return it seems not to call the child component and send the data.
I call this function in parent component:
export const MainMap = () => {
const renderMap = () => {
return(
<GoogleMap
id="map"
onLoad={map => {setMap(map)}}
zoom={15}
center={center}
onZoomChanged={handleZoomChanged}>
</GoogleMap>
)
}
return isLoaded ? renderMap() : 'Loading...'
}
This is the child component:
/* global google */
export const MainMarkersGoogle = ( places ) => {
const [markers, setMarkers] = useState([]);
useEffect(() => {
console.log('child', places)
setMarkers(places)
}, [places])
return(
{markers.map(({ id, geometry, name }) => (
<Marker
key={id}
position={geometry.location}
animation={google.maps.Animation.DROP}
onClick={() => console.log(id) }
title={name}
>
</Marker>
))}
)
}
The console.log from useEffect don't appear and markers are not shown in the map.
I tried to receive the child component with
export const MainMarkersGoogle = ({places}) => {
and
export const MainMarkersGoogle = places => {
getting the same result.
EDIT: I show the function getNearby too, maybe the problem is inside this function but i can't find where:
function getNearby(lat, lng) {
let places;
var config = {
method: 'get',
url: `MY_URL`,
headers: { }
};
axios(config)
.then(function (response) {
console.log(response.data);
for(let i = 0; i < response.data.results.length; i++) {
if(response.data.results[i].business_status === 'OPERATIONAL')
places.push(response.data.results[i])
}
})
.catch(function (error) {
console.log(error);
});
return places;
}
If you need more info of my code, ask me and I will edit this.

Check the docs of Google Maps for react, onZoomChanged is a void event listener. It is not supposed to return an element https://react-google-maps-api-docs.netlify.app . One way to do this would be to update a state variable with the places and have the component already mounted. Of course you can big-brain this and find other ways to achieve the same result. Also you are updating places inside a Promise and return places outside of the Promise so the moment you return them they are empty because the Promise is not resolved. Try to use async-await to achieve a blocking-like effect.

Related

Trying to get data from api and map to another component in React

I'm trying to map an array of movies which I get from an API.
The data is fetched successfully but when I try to map the values and display, it becomes undefined and does not show anything.
I'm new to React so any help and advice would be helpful.
const [items, setItems] = useState([]);
const getMovieData = () => {
axios
.get(api_url)
.then((response) => {
const allMovies = response.data;
console.log(allMovies);
setItems(allMovies);
})
.catch((error) => console.error(`Error: ${error}`));
};
useEffect(() => {
getMovieData();
}, []);
return (
<div>
{items.map((item) => {
<p>{item.title}</p>;
})}
</div>
);
The data is stored like this:
0: {
adult: false,
backdrop_path: '/9eAn20y26wtB3aet7w9lHjuSgZ3.jpg',
id: 507086,
title: 'Jurassic World Dominion',
original_language: 'en',
...
}
You're not returning anything from your map
{
items.map((item) => {
// Add a return
return <p>{item.title}</p>
})
}
First, your items value is an empty array[] as you have initialized with setState([]) and your useEffect() runs only after your component is rendered which means even before you could do your data fetching, your HTML is being displayed inside which you are trying to get {item.title} where your items is an empty array currently and hence undefined. You will face this issue often as you learn along. So if you want to populate paragraph tag with item.title you should fast check if your items is an empty array or not and only after that you can do the mapping as follow and also you need to return the element from the map callback. If it takes some time to fetch the data, you can choose to display a loading indicator as well.
const [items, setItems] = useState([]);
const getMovieData = () => {
axios.get(api_url)
.then((response) => {
const allMovies = response.data;
console.log(allMovies);
setItems(allMovies);
}).catch(error => console.error(`Error: ${error}`));
};
useEffect(() => {
getMovieData();
}, []);
return ( < div > {
items.length !== 0 ? items.map((item) => {
return <p > {
item.title
} < /p>
}) : < LoadingComponent / >
}
<
/div>
);
Good catch by Ryan Zeelie, I did not see it.
Another thing, since you're using promises and waiting for data to retrieve, a good practice is to check if data is present before mapping.
Something like :
return (
<div>
{ (items.length === 0) ? <p>Loading...</p> : items.map( (item)=>{
<p>{item.title}</p>
})}
</div>
);
Basically, if the array is empty (data is not retrieved or data is empty), display a loading instead of mapping the empty array.

Undefined when selecting div with information

Having an issue with a piece of my code. I fetch from flask server, and display with div in React. I want to select the div and have that information pass to a new object array to return back to flask, but I keep getting undefined.
Code snippet:
function PLCPage() {
const [myjunk, setMyjunk] = useState([]);
const [devList, setDevList] = useState ([]);
const Scan = () => {
fetch('/api/home').then(response => {
if(response.status === 200){
return response.json()
}
})
.then(data => setMyjunk(data))
.then(error => console.log(error))
}
const Clear = () => {
setMyjunk({})
}
Creating the divs:
{Object.keys(myjunk).map((key) =>{
return (
<div className='plc-container' key={key} onClick={ReadStuff}>
<h1>ID:{myjunk[key]['name']}</h1>
<h1>IP:{myjunk[key]['IP']}</h1>
</div>
)
Clicking on the div, just to return a console log returns undefined.
const ReadStuff = () => {
console.log(this.IP)
}
I eventually want to return the data I have in the 2 h1 tags to a new object (devList) but I can't even get it to console log. Sorry if this is basic but I've been stumped at this for a week. Thanks
I've tried this.IP, myjunk.IP, this,myjunk.IP. myjunk['IP']. Nothing returns. And when I do myjunk.IP I get "cant read from undefined"
One way to do this is to create a separate component:
const JunkButton = ({junk}) => (
<div className='plc-container' key={key} onClick={() => ReadStuff(junk)}>
<h1>ID:{junk['name']}</h1>
<h1>IP:{junk['IP']}</h1>
</div>
)
Now your map() looks like:
{Object.keys(myjunk).map((key) =>{ <JunkButton junk={ myjunk[key] }/> }
And ReadStuff becomes:
const ReadStuff = (junk) => { console.log(junk) }
Notice how in React we explicitly pass things around as props or function parameters.
first you need to pass myjuck to function and then console it like this:
{Object.keys(myjunk).map((key) =>{
return (
// sending myjuck to function whatever that is
<div className='plc-container' key={key} onClick={() => ReadStuff(myjunk)}>
<h1>ID:{myjunk[key]['name']}</h1>
<h1>IP:{myjunk[key]['IP']}</h1>
</div>
)
ReadStuff function
const ReadStuff = (myjunk) => { console.log(tmyjunk) }

pass arrow function to component prop

I am trying to pass the result of the handleRedirectUrl() function to the ShortUrlField component as a prop.
I don't know what I am doing wrong, please help me
const handleRedirectUrl = () => {
urlService
.getShortenedUrl(urls.slice(-1)[0].short_url)
.then((returnedUrl) => {
setRedirectedUrl(returnedUrl);
})
.catch((error) => {
handleCreateErrors(error);
})
.finally(() => {
return redirectedUrl;
});
};
//display shortened url
const shortUrlDisplay = renderShortUrl ? (
<ShortUrlField
originalUrlValue={urls.slice(-1)[0].original_url}
shortUrlValue={urls.slice(-1)[0].short_url}
redirectedUrlValue={handleRedirectUrl()}
/>
) : (
<EmptyField />
);
The urlService function
const getShortenedUrl = (urlToGet) => {
const request = axios.get(redirectShortenedUrl + `${urlToGet}`);
return request.then((response) => response.data);
};
Edit 1:
I was not returning anything with my handleRedirectUrl function. Also, I was not passing it properly to the props. I have changed my code to
const handleRedirectUrl = () => {
return urlService
.getShortenedUrl(urls.slice(-1)[0].short_url)
.then((returnedUrl) => {
setRedirectedUrl(returnedUrl);
})
.catch((error) => {
handleCreateErrors(error);
})
.finally(() => {
return redirectedUrl;
});
};
//display shortened url
const shortUrlDisplay = renderShortUrl ? (
<ShortUrlField
originalUrlValue={urls.slice(-1)[0].original_url}
shortUrlValue={urls.slice(-1)[0].short_url}
redirectedUrlValue={handleRedirectUrl}
/>
) : (
<EmptyField />
);
It does not work. the getShortenedUrl function is never called
Edit 2: Added the ShortUrlField component code
import React from "react";
const ShortUrlField = (props) => {
return (
<div>
<p>
<a href={props.originalUrlValue}>{props.originalUrlValue}</a> became{" "}
<a href={props.redirectUrlValue}>{props.shortUrlValue}</a>
</p>
</div>
);
};
export default ShortUrlField;
Edit 3: I made it work!!
Many thanks to #ZsoltMeszaros for pointing out the right path to me.
I have passed a state variable to my conditional rendered component, and added an effect hook that basically sets the state if the component is rendered.
Much thanks to all of you that commented.
I didn't understand where is this setRedirectURL function, but anyway your handleRedirectUrl function returns nothing
const handleRedirectUrl = () => {
return urlService
.getShortenedUrl(urls.slice(-1)[0].short_url)
.then((returnedUrl) => {
setRedirectedUrl(returnedUrl);
})
.catch((error) => {
handleCreateErrors(error);
})
.finally(() => {
return redirectedUrl;
});
};
May this can work as you can see like your axios request, this is returning result of urlService.

Component not rendering when using children as function

I am using react-apollo to query the graphQL server and able to successfully hydrate the client with the data. As there will be more than a single place I will be querying for the data I am trying to create a container (refactor) to encapsulate the useQuery hook so that it can be used in one place.
First Try ( working as expected )
const HomeContainer = () => {
const { data, error, loading } = useQuery(GET_DATA_QUERY, {
variables: DATA_VARIABLES
});
const [transformedData, setTransformedData] = useState();
useEffect(() => {
if(!!data) {
const transformedData = someTransformationFunc(data);
setTransformedData(...{transformedData});
}
}, [data]);
if (loading) {
return <div>Loading data ...</div>;
}
if (error) {
return <p>Error loading data</p>;
}
if (!data) {
return <p>Not found</p>;
}
return <Home transformedData={transformedData} />;
};
I wanted to encapsulate the ceremony around different stages of the query to a new container ( loading, error state) so that I can reduce code duplication.
First stab at refactoring
The Query container gets passed in the query, variables and the callback. This takes the responsibility of returning different nodes based on the state of the query ( loading, error or when no data comes back ).
const HomeContainer = () => {
const {data, error, loading} = useQuery(GET_DATA_QUERY, {
variables: DATA_VARIABLES
});
const [transformedData, setTransformedData] = useState();
const callback = (data) => {
const transformedData = someTransformationFunc(data);
setTransformedData(...{
transformedData
});
};
return (
<QueryContainer
query={GET_DATA_QUERY}
variables={DATA_VARIABLES}
callback ={callback}
>
<Home transformedData={transformedData} />
</QueryContainer>
)
};
const QueryContainer = ({callback, query, variables, children }) => {
const {data, error, loading } = useQuery(query, {
variables: variables
});
// Once data is updated invoke the callback
// The transformation of the raw data is handled by the child
useEffect(() => {
if (!!data) {
callback(data);
}
}, [data]);
if (loading) {
return <div > Loading data... < /div>;
}
if (error) {
return <p > Error loading data < /p>;
}
if (!data) {
return <p > Not found < /p>;
}
return children;
};
QueryContainer is using useEffect and invokes the callback when data comes back. I felt this is a bit messy and defeats the purpose of encapsulating in the parent and using the callback to talk and update the child.
Third Try ( Using children as function )
Got rid of the callback and passing the data as the first argument to the children function.
const HomeContainer = () => {
return (
<QueryContainer
query={GET_DATA_QUERY}
variables={DATA_VARIABLES}
>
{(data) => {
const transformedData = someTransformationFunc(data);
return <Home transformedData={transformedData} />;
}}
</QueryContainer>
)
};
const QueryContainer = ({ query, variables, children }) => {
const { data, error, loading } = useQuery(query, {
variables: variables
});
if (loading) {
return <div>Loading data ...</div>;
}
if (error) {
return <p>Error loading data</p>;
}
if (!data) {
return <p>Not found</p>;
}
return children(data);
};
I expected this to work as nothing really changed and the new render when the data is updated calls the children as a function with data as argument.
But when I navigate to that route I see a black screen ( no errors and I can see the correct data logged into the console )
If I click the link again I can see the component committed to the DOM.
Not really sure what is going on here and wondering if someone can throw light as to what is going on here.
hmmm, should work ...
Try something like this (component injection, a bit like HOC - inspiration) :
const HomeContainer = () => {
return (
<QueryContainer
query={GET_DATA_QUERY}
variables={DATA_VARIABLES}
transformation={someTransformationFunc}
renderedComponent={Home}
/>
)
};
const QueryContainer = ({ query, variables, transformation, renderedComponent: View }) => {
const { data, error, loading } = useQuery(query, { variables });
if (loading) {
return <div>Loading data ...</div>;
}
if (error) {
return <p>Error loading data</p>;
}
if (!data) {
return <p>Not found</p>;
}
// let transformedData = transformation(data);
// return <View transformedData={transformedData} />;
return <View transformedData={transformation ? transformation(data) : data} />;
};
If still not working (!?), pass both data and transformation as props and use them to initialize state (with useState or useEffect).
Do you really/still need <HomeContainer/> as an abstraction? ;)
The code snippets that I have added above is working as expected in isolation.
https://codesandbox.io/s/weathered-currying-4ohh3
The problem was with some other component down the hierarchy tree that was causing the component not to re render.
The 2nd implementation is working as expected as the component is getting rendered again dud to the callback being invoked from the parent.

How to pass react hook state to another component for each click

I stuck in this moment creating store with different products, that I want to add to the basket. The problem occur when I wanted to pass the state of cardList into Basket component to change the information from "Basket is empty" to display information how many items are currently in basket.
Below I paste my main hooks component with basket component which include all functionality.
Basket component:
import React from 'react'
const Basket = (props) => {
return (
<div>
{props.cardItems.length === 0 ? "Basket is empty" : <div> You have {props.cardItems.length} products in basket!</div>}
</div>
)
}
export default Basket;
Main component:
function
const [cardItems, setCardItems] = useState([]);
const price = 2.50;
useEffect(() => {
fetch(URL, {
method: 'GET',
headers: {
Accept: "application/json",
}
}).then(res => res.json())
.then(json => (setBeers(json), setFilteredBeers(json))
);
}, [])
function handleMatches(toMatch) {...
}
const displayFilterBeers = event => {...
}
const handleRemoveCard = () => {...
}
const handleAddToBasket = (event, beer) => {
setCardItems(state => {
let beerAlreadyInBasket = false;
cardItems.forEach(item => {
if (item.id === beer.id) {
beerAlreadyInBasket = true;
item.count++;
};
});
if (!beerAlreadyInBasket) {
cardItems.push({ ...beer, count: 1 })
}
localStorage.setItem('baketItems', JSON.stringify(cardItems));
console.log('cardItems: ', cardItems, cardItems.length);
return cardItems;
})
}
return (
<div className="App">
<div className='search'>
<input type='text' placeholder='search beer...' onChange={displayFilterBeers} />
</div>
<BeerList BeersList={filteredBeers} price={price} handleAddToBasket={handleAddToBasket} />
<Basket cardItems={cardItems}/>
</div>
);
}
export default App;
I saw an example that without React hooks that in basket component someone used const {cartItems} = this.props; but I don't know how to achieve something similar using hooks.
I think what you are facing is related to this issue.
So when dealing with array or list as state, react doesn't re-render if you don't set state value to a new instance. It assumes from the high-level comparison that the state hasn't been changed. So it bails out from the re-rendering.
from the issue I found this solution is better than the others -
const handleAddToBasket = (event, beer) => {
const nextState = [...cardItems, beer] // this will create a new array, thus will ensure a re-render
// do other stuffs
setCardItems(nextState);
};

Resources