React how to render dynamic images inside nested data objects - reactjs

I can console.log() the data I want after mapping through my data from my GraphQL query. However, the nested .map functions are not rendering my JSX. Is it possible to render JSX in nested .maps?
const NikonGallery = ({ data }) => {
return (
<Layout>
{data.allFiles.nodes.map((item) => {
Object.entries(item).map(([key, value]) => {
value.map((image) => {
console.log("Individual image", image) // Logs show the data I want
return (
<>
<GatsbyImage
image={image.gatsbyImageData}
alt={image.description}
/>
</>
)
})
})
})}
</Layout>
)
}
export default NikonGallery
The data from GraphQL/Contentful is a nested array of objects. I'm having trouble getting the JSX to render when I call the nested objects via .map.

Regarding the nested maps, you'll need to make some changes to return a proper expression from each map. The reason the console log works is because the code still loops; however, no expression is returned from the map for React to render. Try this:
data.allFiles.nodes.map((item) => {
return Object.entries(item).map(([key, value]) => {
return value.map((image) => {
console.log("Individual image", image) // Logs show the data I want
return (
<>
<GatsbyImage
image={image.gatsbyImageData}
alt={image.description}
/>
</>
)
})
})
})}
</Layout>
)
When using the Gatsby image plugin for dynamic images such as this, you should use the getImage() method provided by the plugin. The import should look like this:
import { GatsbyImage, getImage } from "gatsby-plugin-image";
And the usage in your case would look something like this:
value.map((image) => {
const gatsbyImage = getImage(image);
return (
<>
<GatsbyImage
image={gatsbyImage}
alt={image.description}
/>
</>
)
})

Related

Cannot return the content from a nested array in React JS

I am trying to display the data I fetched from an API which is a nested array. The json file looks like this for one pool and the devices inside that pool:
[
{
"id": "staging",
"pool": "some name",
"status": "FAILED",
"deviceMonitoringEntries": [
{
"deviceDescriptor":{
"id": "Apple_TV_HD1",
}
]
}
]
I want to display the id of the pool first and then display the devices assigned to the pool by displaying the id in deviceDescriptor.
My code is like this:
import React, { useEffect, useState } from 'react'
import axios from 'axios'
function Pool(){
const url = 'http://localhost:8043/pools'
const [pool, setPool] = useState(null)
let content = null
useEffect(() => {
axios.get(url)
.then(response =>{
setPool(response.data)
})
}, [url])
if(pool){
console.log("in if pool")
console.log(pool)
return (
content=
<>
{pool.map((id) => {
<h3 key = {id}>{id}</h3>
return (
<>{pool.map(({ deviceMonitoringEntries}) => (
deviceMonitoringEntries.map((deviceDescriptor) => (
<p key = {deviceDescriptor.id}> {deviceDescriptor.id}</p>
))
))}</>
);
})}
</>
)
}
return(
<div>
{content}
</div>
)
}
export default Pool
However, the header <h3 key = {id}>{id}</h3> never prints. I can display the header and paragraph separately but it does not work together. I am very new to React and I would appreciate your help!
As per React documentation
React components implement a render() method that takes input data and returns what to display.
In the functional component, you directly add a return statement at the end.
You cannot assign variables like this in your code.
return (
content=
<>
...
</>
You got the response and you stored that value in a state variable. Use the variable and render your content in the final return statement.
{
pool.map((p) => (
<>
<h3 key={p.id}>{p.id}</h3>
{p.deviceMonitoringEntries.map((device) => (
<p key={device?.deviceDescriptor?.id}>{device.deviceDescriptor?.id}</p>
))}
</>
));
}
You can try like that in your code and I am attaching a sandbox for reference here.

How to execute useEffect only once inside looped component

I have component where I have array of data that is being looped using map and rendered a new component base one that and inside the looped component I have a useEffect that fetches the data from the api but it runs same api twice.
Here is the code
I am looping through array of rule_set_versions which is in this case size of 2
const ExpandedContent = ({ experiment }) => {
return experiment.rule_set_versions &&
experiment.rule_set_versions.map((ruleSetVersion) => <RuleSetVersionCollapse key={ruleSetVersion.id} ruleSetVersion={ruleSetVersion} />)
}
const ExperimentsCollapse = ({ experiment }) => {
return <React.Fragment>
<div className={styles.experiment_collapse_root}>
<Collapse>
<Collapse.Panel className={styles.experiment_item} extra={<ExtraTest experiment={experiment} />}>
<ExpandedContent experiment={experiment} />
</Collapse.Panel>
</Collapse>
</div>
</React.Fragment>
}
Here is my RuleSetVersionCollapse snippet
const ruleSet = useSelector(state => state.ruleSet)
React.useEffect(() => {
if (!ruleSet.id) {
dispatch(getRuleSetAction(ruleSetVersion.rule_set_id))
}
}, [dispatch])
And the useEffect runs twice even though the ruleSetVersion.rule_set_id is same on both the case.
Can anyone suggest any way I can solve this issue.
Thanks

How to fix the error "react use hook cannot be called inside a callback function" using react?

i am using useHook named useGetCompanyByItemId in the return statement.
and so i am getting the error
"react hook cannot be called in a callback function"
what i am trying to do?
i am querying for owneditems and shareditems.
and i display both the items. in the Content div i do mapping and there i am calling the useGetCompanyByItemId hook and i get the error.
below is my code,
function Parent() {
const ownedItems = [{ //somearray of objects}];
const sharedItems = [{//somearray of objects}];
const getCurrentItems = () => {
return ownedItems.concat(sharedItems);
}
return (
<Wrapper>
{getCurrentItems.length> 0 &&
<FirstWrapper>
//somedivs
</FirstWrapper>
<Content>
{springProps.map((index) => {
const item = getCurrentItems()[index];
const isSharedItem = item && item.cognitoId !== cognitoId;
const company = useGetCompanyByItemId(item.id); //here is the error
return (
<>
{isSharedItem &&
<div>
<span>company</span>
</div>
}
</>
}
)
}
);
</Content>
</Wrapper>
);
}
i am not sure how to fix this. i need to pass the item.id for the useGetCompanyById hook and i dont know how to pass that item.id from outside the return statement since that would fix that error.
could someone help me fix this error. thanks.
Extract this logic to a component
function Item({ item, isSharedItem }) {
const company = useGetCompanyByItemId(item.id);
return (
<>
{isSharedItem && (
<div>
<span>company</span>
</div>
)}
</>
);
}
and then use it in your loop
springProps.map((index) => {
...
return <Item item={item} isSharedItem={isSharedItem} key={index} />
I can see two ways of refactoring here:
Option 1: If you dont have control over the custom hook to modify
Extract the iteration into a component:
const Company = ({itemId, isSharedItem}) => {
const company = useGetCompanyByItemId(itemId);
return (<>
{isSharedItem &&
(<div>
<span>{company}</span>
</div>)
}
</>);
}
Use the above component while you iterate.
Option 2: If you have control over the custom hook:
I would recommend to refactor custom hook to return a method than object. Sample usage:
const {getCompanyByItemId} = useFetchCompany();
.
.
.
anywhere in the code,
getCompanyByItemId(itemId)
Obvious advantage with above option:
Readable and extendable and use it anywhere and even pass around
You don't have to worry about refactoring and code splitting just not to break hook rules(do so if it makes sense ofcourse).

Adding wrapper for graphQL queries - HOC or Render props

I am trying to do a wrapper for the graphQL queries, I tried this
const GQLWrapper = ({ query, children}) => (
<Query query={query}>
{({ loading, error, data }) => {
if (loading) {
return null
}
if (error) {
<QueryError />
}
const { gqlData } = data.page
return (
<div>
{children}
</div>
)
}}
</Query>
)
but i don't understand how to use render props to pass the data to the child component.
Also if it is a better solution to use HOC, please let me know (when should one be used or the other). Thank you
You just make your children a function and pass the data there
return <div>{children(gqlData)}</div>;
When you use your wrapper you do:
<GQLWrapper query={myquery}>
{(gqlData) => <SomeComponent data={gqlData} />}
</GQLWrapper>

Object into an array then return a component - React JS Application

Evening fellow developers,
I've rewrote a React JS application, but I've just hit a brick wall.
I have a form which adds some data to a database, specifically Firebase googles very own cloud based database solution. However, now I'm trying to fetch the data and render the components below but I'm sure how to do this.
What I receive from Firebase as a response :
Response From Firebase
Currently I'm just logging the response to show I do receive a response, I have set the response to an empty state. On render it gets set to the response from the server.
I want to be able to now convert the objects into an array which can then return an array of responses. The array will then be looped through and transformed into components which will get rendered below as I specified above.
Can someone assist me with this task as I'm very unsure how to accomplish this task. I will appreciate any responses to my question.
Github Link: https://github.com/AlexMachin1997/React-JS-Contact-Book
What i have so far:
componentDidMount () {
axios.get("/contact.json")
.then(response => {
this.setState({
contactsArray: response.data.person
})
console.log(this.state.contactsArray)
})
//Any Errors The Error State Is Set To True
.catch (error => {
console.log(error)
})
}
To convert the response object into an array you can use Object.keys or Object.values
Object.values(firebaseResponse);
or
Object.keys(firebaseResponse).map((x) => {
// additional transformation
return x;
});
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_objects/Object/values
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/keys
You can use Object.values or Object.keys to get an array from your object. But Object.keys is more suitable if you don't have unique id's in your persons object since you will need a key to iterate a JSX prop or your component. If response is in your root state:
render() {
const obj = this.state;
return (
Object.keys( obj ).map( objKey =>
(
<div key={objKey}>
<p>Name: {obj[objKey].persons.name}</p>
<p>Email: {obj[objKey].persons.email}</p>
<p>Phone: {obj[objKey].persons.phone}</p>
</div>
)
)
);
}
or pass it to another component:
render() {
const obj = this.state;
return (
Object.keys(obj).map(objKey =>
<Person obj={obj} objKey={objKey} />
)
);
}
With Object.values this will be slightly cleaner but you should use something unique for your key. I've chosen email property since emails are unique.
render() {
const obj = this.state;
return (
Object.values(obj).map(value =>
(
<div key={value.persons.email}>
<p>Name: {value.persons.name}</p>
<p>Email: {value.persons.email}</p>
<p>Phone: {value.persons.phone}</p>
</div>
)
)
);
}
or again with a component:
render() {
const obj = this.state;
return (
Object.values(obj).map(value =>
<Person value={value} />
)
);
}

Resources