Very new to React, so I might be approaching this the wrong way... I want my app to take input from a text input field, retrieve a JSON from the reddit API (the url is built from the text input), and then render data from the JSON, looping through each of the entries. I'm using useState to trigger the data render. I can successfully retrieve the data and output specific values, but I want to be able to have a loop that dynamically outputs the data into various HTML elements.
Here's what I have so far that allows me to output some specific values as an example:
import React, { useState } from 'react';
const App = () => {
const [retrievedData, setRetrievedData] = useState([])
const runSearch = async() => {
const searchInput = document.getElementById('searchInput').value
const searchUrl = 'https://www.reddit.com/r/' + searchInput + '/new/.json?limit=5'
const response = await fetch(searchUrl)
const redditResponse = await response.json()
setRetrievedData(<>
<p>{JSON.stringify(redditResponse.data.children[0].data.author)}</p>
<p>{JSON.stringify(redditResponse.data.children[0].data.title)}</p>
</>)
}
return (
<>
<section>
<input type="text" id='searchInput' placeholder='Enter a subreddit...'></input>
<button onClick={runSearch}>
Get Data
</button>
<div>{retrievedData}</div>
</section>
</>
);
};
export default App;
And here's an example of the JSON that is retrieved from the reddit API, just stripped down with only the example values I use in my code above:
{
"kind": "Listing",
"data": {
"modhash": "",
"dist": 5,
"children": [
{
"kind": "t3",
"data": {
"author": "author1",
"title": "title1"
}
},
{
"kind": "t3",
"data": {
"author": "author2",
"title": "title2"
}
},
{
"kind": "t3",
"data": {
"author": "author3",
"title": "title3"
}
},
{
"kind": "t3",
"data": {
"author": "author4",
"title": "title4"
}
},
{
"kind": "t3",
"data": {
"author": "author5",
"title": "title5"
}
}
],
"after": "t3_jnu0ik",
"before": null
}
}
I just need the final rendered output to be something like:
<h2>TITLE 1</h2>
<h4>AUTHOR 1</h4>
<p>SELFTEXT 1</p>
...and repeated for each post data that is retrieved.
I've seen a variety of different ways to render JSON data and many of them show either loops and/or the .map() method, but I can't ever seem to get those to work, and wonder if it's an issue with the useState. Perhaps there is some way I should be rendering the data some other way?
You don't need set jsx to state, you can directly iterate children data with map
Try this
const App = () => {
const [retrievedData, setRetrievedData] = useState([])
const runSearch = async() => {
const searchInput = document.getElementById('searchInput').value
const searchUrl = 'https://www.reddit.com/r/' + searchInput + '/new/.json?limit=5'
const response = await fetch(searchUrl)
const redditResponse = await response.json()
if (redditResponse.data.children && redditResponse.data.children.length) {
setRetrievedData(redditResponse.data.children)
}
}
return (
<>
<section>
<input type="text" id='searchInput' placeholder='Enter a subreddit...'></input>
<button onClick={runSearch}>
Get Data
</button>
<div>
{
retrievedData.map((children, index) => {
return (
<div key={children.data.author + index}>
<div>Kind: { children.kind }</div>
<div>Author: { children.data.author }</div>
<div>Title: { children.data.title }</div>
</div>
)
})
}
</div>
</section>
</>
);
};
Related
Here's what I am trying to do. I am getting a bunch of objects back from a JSON response (locally). I want to display the first let's say 12 coming back but want to add a "see more" link to show the next series of 12. I know you can split arrays but in my case, not sure if I should even split them rather just have the "see more" link show the next 12 in the array of objects coming back. I imagine it's creating a function that calls 12 (or whatever number) at a time or something when you click the link. hope that makes sense. Thanks
Release component (looping through JSON to display releases)
import releases from "../data/releases.json";
import styled from 'styled-components';
import { Link } from 'react-router-dom';
import '../main.css'
const Release = () => {
//experimenting with splitting up array but not implemented yet
function sliceIntoChunks(arr, chunkSize) {
const res = [];
for (let i = 0; i < arr.length; i += chunkSize) {
const chunk = arr.slice(i, i + chunkSize);
res.push(chunk);
}
return res;
}
console.log(sliceIntoChunks(releases, 3));
return (
<Wrapper>
<div className="release fadein">
{releases.map((release, i) => (
<div className="item" key={i}>
<Link to={`/release/${release.id}`}>
<img src={release.imageURL} alt={release.artist} />
</Link>
</div>
))}
{/* Implement here to show the next 12 in the array*/}
See more...
</div>
</Wrapper>
)
}
Json file (showing the first 3 objects for brevity but there's say 40 or 50 that will be coming back)
[
{
"id": 0,
"artistID": "ES",
"imageURL": "../images/releases/cloudbursts.jpg",
"title": "Cloud Bursts",
"artist": "ERIC SHANS",
"description": "One of the main 3Bridge Records bosses Eric Shans is back with his first original music of 2022, 'Cloud Bursts'. .",
"buy": "https://www.beatport.com/release/ardour/3096956",
"stream": "https://open.spotify.com/album/5vNYmx4uNY5fIPrOCR0cUa?si=lwCXwLGtSn6g6ZNToPZKRQ"
},
{
"id": 1,
"artistID": "ES",
"imageURL": "../images/releases/archtypes.jpg",
"title": "Archetypes",
"description": "Mexico City based artist Eisenheim is back on 3Bridge with a killer EP called 'Archetypes'.n",
"artist": "ERIC SHANS",
"buy": "https://www.beatport.com/release/we-choose/1916121",
"stream": "https://open.spotify.com/album/4cutoknbgciTGGpAYSZXfK?si=gsNvR6ytTN6KdrPORfSLOg"
},
{
"id": 2,
"artistID": "BG",
"imageURL": "../images/releases/pingpong.jpg",
"title": "Ping Pong",
"artist": "SIMON SAMPLER & FISCHMEHL",
"description": "We welcome Simon Sampler back to the label for a great 2 tracker featuring Austrian producer Fischmehl as well.",
"buy": "https://www.beatport.com/release/cloud-bursts/3687306",
"stream": "https://open.spotify.com/album/4cutoknbgciTGGpAYSZXfK?si=gsNvR6ytTN6KdrPORfSLOg"
}
]
Would recommend having a simple button that add extra 12 items to a state when clicking. It's easily done by using the .slice() array method:
import React from "react";
const STEP = 12;
const data = [...Array(100).keys()].map((id) => ({ id }));
export default function App() {
const [items, setItems] = React.useState(data.slice(0, STEP));
const loadMore = () => {
setItems([...items, ...data.slice(items.length, items.length + STEP)]);
};
return (
<div className="App">
<ul>
{items.map((item) => {
return <li>{item.id}</li>;
})}
</ul>
<button onClick={loadMore}>Load more</button>
</div>
);
}
Use usestate hook to extend
Const [present ,setPresent]=useState(12)
releases.slice(0, present).map(()=>
return div here) )
<a onClick={()=>setPresent (present+12)}
See more
I recursively mapped nested JSON, and console log output all elements in format: property => value correctly, but components do not render. Following is JSON:
{
"index": "dwarf",
"name": "Dwarf",
"speed": 25,
"ability_bonuses": [
{
"ability_score": {
"index": "con",
"name": "CON",
"url": "/api/ability-scores/con"
},
"bonus": 2
}
],
"alignment": "Most dwarves are lawful, believing firmly in the benefits of a well-ordered society. They tend toward good as well, with a strong sense of fair play and a belief that everyone deserves to share in the benefits of a just order.",
"age": "Dwarves mature at the same rate as humans, but they're considered young until they reach the age of 50. On average, they live about 350 years.",
"size": "Medium",
"size_description": "Dwarves stand between 4 and 5 feet tall and average about 150 pounds. Your size is Medium.",
"starting_proficiencies": [
{
"index": "battleaxes",
"name": "Battleaxes",
"url": "/api/proficiencies/battleaxes"
},
{
"index": "handaxes",
"name": "Handaxes",
"url": "/api/proficiencies/handaxes"
},
{
"index": "light-hammers",
"name": "Light hammers",
"url": "/api/proficiencies/light-hammers"
},
{
"index": "warhammers",
"name": "Warhammers",
"url": "/api/proficiencies/warhammers"
}
],
"starting_proficiency_options": {
"choose": 1,
"type": "proficiencies",
"from": [
{
"index": "smiths-tools",
"name": "Smith's tools",
"url": "/api/proficiencies/smiths-tools"
},
{
"index": "brewers-supplies",
"name": "Brewer's supplies",
"url": "/api/proficiencies/brewers-supplies"
},
{
"index": "masons-tools",
"name": "Mason's tools",
"url": "/api/proficiencies/masons-tools"
}
]
},
"languages": [
{
"index": "common",
"name": "Common",
"url": "/api/languages/common"
},
{
"index": "dwarvish",
"name": "Dwarvish",
"url": "/api/languages/dwarvish"
}
],
"language_desc": "You can speak, read, and write Common and Dwarvish. Dwarvish is full of hard consonants and guttural sounds, and those characteristics spill over into whatever other language a dwarf might speak.",
"traits": [
{
"index": "darkvision",
"name": "Darkvision",
"url": "/api/traits/darkvision"
},
{
"index": "dwarven-resilience",
"name": "Dwarven Resilience",
"url": "/api/traits/dwarven-resilience"
},
{
"index": "stonecunning",
"name": "Stonecunning",
"url": "/api/traits/stonecunning"
},
{
"index": "dwarven-combat-training",
"name": "Dwarven Combat Training",
"url": "/api/traits/dwarven-combat-training"
},
{
"index": "tool-proficiency",
"name": "Tool Proficiency",
"url": "/api/traits/tool-proficiency"
}
],
"subraces": [
{
"index": "hill-dwarf",
"name": "Hill Dwarf",
"url": "/api/subraces/hill-dwarf"
}
],
"url": "/api/races/dwarf"
}
Then this is code:
import React, {Component} from 'react'
import { Grid, Header, Label } from 'semantic-ui-react'
import TypeComponent from './type_component'
import TestComponent from './test_component'
class raceWindow extends Component {
constructor(props)
{
super(props)
this.state = {
data: {}
}
}
componentDidMount()
{
fetch(this.props.hdAPI)
.then(response=>response.json())
.then(data => {this.setState({data: data})});
this.setState({hdAPI: this.props.hdAPI});
}
componentDidUpdate(prevProps)
{
if(this.props.hdAPI !== prevProps.hdAPI)
{
fetch(this.props.hdAPI)
.then(response=>response.json())
.then(data => {this.setState({data: data})});
this.setState({hdAPI: this.props.hdAPI});
}
}
isType (attr, value)
{
if(Array.isArray(value))
{
value.map((v) => {
Object.entries(v).map(([a1,v1]) => this.isType(a1,v1))
})
}
else
{
if(typeof value === 'object')
{
Object.entries(value).map(([a,v]) => this.isType(a,v))
}
else
{
console.log(attr);
console.log(value);
return(<Grid.Column><Label>{attr}</Label>{value}</Grid.Column>);
}
}
};
render()
{
const { data} = this.state;
//I also tried to do the recursive map in a component, but it does not work either
/*
return(
<div>
<Grid container columns = {10}>
<TypeComponent attr = {""} value = {data} />
</Grid>
</div>
);*/
return(
<div>
<Grid container columns = {10}>
{this.isType("",data)}
</Grid>
</div>
);
}
}
export default raceWindow
When I tried to map it manually in the first layer, only first layer components rendered. Nested data in deeper layers do not render, though console output the deeper data correctly. So I assume react do not render deeper component. How should I deal with this?
Edit: Just note, I may be wrong, but I think every loop of isType() will go to this part of code at last:
else
{
console.log(attr);
console.log(value);
return(<Grid.Column><Label>{attr}</Label>{value}</Grid.Column>);
}
console also output all non-array, non-object value in the log, but return() in this block does not render.
First let's look at the main problems you have right now:
The if blocks inside your isType function do not return anything. You are running functions against the value argument, but then you're not doing anything with what those functions return. Fixing this can be as simple as changing value.map... to value = value.map..., but if you don't do something, then when you get to the return at the end, those if blocks will have no impact on the final result.
Inside the first if block, your value.map... function doesn't return anything since you have brackets around Object.entries.... You just need to return Object.entries..., or else remove the brackets.
If we fix those problems and simplify the if/else logic a bit, we end up getting something back in the JSX:
isType(attr, value) {
let returnValue = value;
if (Array.isArray(returnValue)) {
returnValue = value.map((v) => {
return Object.entries(v).map(([a1, v1]) => this.isType(a1, v1));
});
} else if (typeof returnValue === "object") {
returnValue = Object.entries(value).map(([a, v]) => this.isType(a, v));
}
return (
<Grid.Column>
<Label>{attr}</Label>
{returnValue}
</Grid.Column>
);
}
But you'll see when you get this far that the resulting DOM probably isn't what you want.
Instead, I recommend changing the approach a bit so that we first convert our JSON into a structure we can use and then map over it in our render function. This will hopefully also make it a little easier to reason about what is happening when and to modify your function to only add things to the final output that you actually care about.
That'd look something like this:
class RaceWindow extends Component {
constructor(props) {
super(props);
// your JSON; store in state if necessary
this.data = {};
// create a placeholder variable
this.finalDom = [];
// call `this.isType` to fill in that variable
// the result is a giant array of shape {attr: 'str', value: 'str'}
this.isType("", this.state.data);
}
isType(attr, value) {
if (typeof value === "string") {
this.finalDom.push({
attr,
value
});
}
if (Array.isArray(value)) {
this.finalDom.push({
attr,
value: value.map((v) => {
return Object.entries(v).map(([a1, v1]) => this.isType(a1, v1));
})
});
}
if (typeof value === "object") {
this.finalDom.push({
attr,
value: Object.entries(value).map(([a, v]) => this.isType(a, v))
});
}
}
render() {
return (
<div>
<div class="container">
{this.finalDom.map((obj) => (
<div class="item">
<div class="label">{obj.attr}</div>
{obj.value}
</div>
))}
</div>
</div>
);
}
}
CodeSandbox demo of the above.
Final note: Make sure you capitalize the first letter of your component, or else React won't recognize it as a component.
Following is working code:
import React, {Component} from 'react'
import { Grid, Header, Label } from 'semantic-ui-react'
import TypeComponent from './type_component'
import TestComponent from './test_component'
class raceWindow extends Component {
constructor(props)
{
super(props)
this.state = {
data: {}
}
}
componentDidMount()
{
fetch(this.props.hdAPI)
.then(response=>response.json())
.then(data => {this.setState({data: data})});
this.setState({hdAPI: this.props.hdAPI});
}
componentDidUpdate(prevProps)
{
if(this.props.hdAPI !== prevProps.hdAPI)
{
fetch(this.props.hdAPI)
.then(response=>response.json())
.then(data => {this.setState({data: data})});
this.setState({hdAPI: this.props.hdAPI});
}
}
isType (attr, value, obj)
{
if(Array.isArray(value))
{
value.map((v) => {
Object.entries(v).map(([a1,v1]) => this.isType(a1,v1,obj))
})
}
else
{
if(typeof value === 'object')
{
Object.entries(value).map(([a,v]) => this.isType(a,v,obj))
}
else
{
var pair = {}
pair[attr] = value
obj.push(pair);
}
}
};
render()
{
const { data} = this.state;
var obj = [];
return(
<div>
<Grid container columns = {5}>
{this.isType("",data,obj)}
{
obj.map((arr, i) => {
return(
Object.entries(arr).map(([a,v]) => {
return(
<Grid.Column key = {i}><Label key = {i} color = 'orange'>{a}</Label>{v}</Grid.Column>
);
})
);
})
}
</Grid>
</div>
);
}
}
export default raceWindow
Thanks cjl750's advice, I use an variable to collect results from recursive function and it works. I also tried to let very if block has a return as his advice, but components still do not render. Not sure why.
For now, it seems jsx returned from deep layer in recursive function do not render.
I created a child component by react-select, but options don't show up in first click on selector.
For the second time each section is clicked, the options are displayed. I tried to use the AsyncSelect but again it did not work.
The Data is read from Local Storage, but I don't think there is a problem with this.
Sandbox:
https://codesandbox.io/s/strange-field-4elv3?file=/src/App.js
My data from local storage:
const feauters = [
{
"id": 5,
"title": "Type",
"options": [
{
"id": 231040,
"name": "cloth",
"property": 5
},
{
"id": 230081,
"name": "Synthetic materials",
"property": 5
}
]
},
{
"id": 646,
"title": "Shoe soles",
"options": [
{
"id": 231063,
"name": "Abrasion resistant",
"property": 646
},
{
"id": 231064,
"name": "Reduce the pressure",
"property": 646
}
]
},
]
Parent Component:
<MultiSelect features={features} />
My Component:
import React, {useEffect, useState} from 'react';
import {Form} from 'react-bootstrap';
import Select from 'react-select';
const MultiSelect = ({features}) => {
// Declare States
const [selectors, setSelectors] = useState([]);
// Handle Features
useEffect(() => {
const initialSelectors = features.map((item, index, array) => {
const options = item.options.map((subItem) => {
return {
value: `${subItem.property}-${subItem.id}`,
label: subItem.name,
};
});
return (
<React.Fragment key={`product-multiselect-${index}-${item.id}`}>
<Form.Label htmlFor={`product-multiselect-${index}-${item.id}`}>{item.title}</Form.Label>
<Select
id={`product-multiselect-${index}-${item.id}`}
className="mb-2"
classNamePrefix="select"
defaultInputValue="Select..."
placeholder="Select..."
noOptionsMessage={() => 'Not Found.'}
isMulti
isClearable
isRtl
isSearchable
name={item.title}
onChange={handleChangeInput}
options={options}
/>
</React.Fragment>
);
});
setSelectors(initialSelectors);
}, [features]);
// Handle Change Input
const handleChangeInput = (values) => {
console.log(values);
};
return selectors;
};
export default MultiSelect;
First of all as mentioned in the comments you shouldn't store the component inside the state. Related question
Secondary, options don't show up because of defaultInputValue props. If you remove it, the component would work as intended
What I am trying to do is fetch the inner data of blog_set. But in my case, I'm getting a null value (usually nothing is output).
Is this the correct way to get the value: {bloglist.blog_set.title} ?
api-data:
[
{
"url": "http://localhost:8000/api/category/brown",
"id": 1,
"title": "brown",
"slug": "brown",
"image": "http://localhost:8000/media/category/bg_1.jpg",
"description": "",
"created_on": "2020-05-08T15:21:02Z",
"status": true,
"blog_set": [
{
"id": 6,
"url": "http://localhost:8000/api/blog_detail/test3",
"title": "test3",
"slug": "test3",
"image": "http://localhost:8000/media/blog/author.jpg",
"description": "test3",
"created_on": "2020-05-13T13:36:45Z",
"status": true,
"category": [
1
]
}
]
}
]
./src/Category.js
export default class App extends Component{
state = {
bloglist: [],
};
componentDidMount() {
this.fetchData();
}
fetchData = async () => {
try {
const response = await fetch("http://localhost:8000/api/category");
const jsonResponse = await response.json();
this.setState({ bloglist: jsonResponse });
} catch (error) {
console.log(error);
}
};
render(){
{
const { bloglist } = this.state;
return(
<div>
{bloglist.map((bloglist) => (
<div>
<h3 class="mb-2">{bloglist.blog_set.title}</h3>
</div>
))}
</div>
);
}
}
}
blog_set is an array so it does not have an attribute/memeber/item called title. You should define at what index you want the data.
bloglist.blog_set[0].title
Or iterate over blog_set too
As bloglist is also an array you need to use one more .map() or as bloglist[0].blog_set[0].title
Example:
{bloglist.map((bloglist) => (
<div>
<h3 class="mb-2">{bloglist.blog_set.map(i=> i.title)}
</h3>
</div>
))}
blogList.map() will iterate the parent Array of objects to get blog_set and blog_set.map() will now iterate the blog_set to get list title
{bloglist.map((bloglist) =>(
<div>
<h3 class="mb-2">{bloglist.blog_set.map((list)=>( list.title)}</h3>
</div>)}
blog_set is an array. In order to iterate it, use map and {title}. In each iteration of your blog_set object, there is a key named title (destructured object).
<div>
{bloglist.map((bloglist) => (
<div>
<h3 class="mb-2">{blog_set.map(({title})=>title))}</h3>
</div>
))}
</div>
I have a problem rendering my props data
Here I'm trying to pass props to a component with mapped data from a sample data set
const weatherComponents = weatherData.items.map(weather => {
return(
<div key={weather.forecasts.area}>
<WeatherForecast
name={weather.forecasts.area}
condition={weather.forecasts.forecast}>
</WeatherForecast>
</div>
)})
return(
{weatherComponents} )
This is the component
function WeatherForecast(props) {
return(
<div>
<p>Name: {props.name}</p>
<p>Condition: {props.condition}</p>
</div>
)}
This is the sample data set
{
"area_metadata": [
{
"name": "Yishun",
"label_location": {
"latitude": 1.418,
"longitude": 103.839
}
}
],"items": [
{
"forecasts": [
{
"area": "Yishun"
"forecast" : "cloudy"
}
]}
]}
In my browser, it shows Warning: Each child in a list should have a unique "key" prop. and the data are not rendering, it only appears "Name: " without the area name from the data set. Am I mapping the data in the wrong way? Help TT
You have 2 options ... well, 3.
You need an array in "area_metadata" and "items":
1.1. The fast solution:
const weatherComponents = weatherData.items.map(weather => {
return(
<div key={weather.forecasts[0].area}>
<WeatherForecast
name={weather.forecasts[0].area}
condition={weather.forecasts[0].forecast}>
</WeatherForecast>
</div>
)
})
return({weatherComponents})
1.2 The right solution:
const weatherComponents = weatherData.items.map(weather => {
return weather.forecasts.map( casts => (
<div key={casts.area}>
<WeatherForecast
name={casts.area}
condition={casts.forecast}>
</WeatherForecast>
</div>
))
})
return({weatherComponents})
2. You do not need an array:
{
"area_metadata": [
{
"name": "Yishun",
"label_location": {
"latitude": 1.418,
"longitude": 103.839
}
}
],
"items": [
{
"forecasts": {
"area": "Yishun"
"forecast" : "cloudy"
}
}
]
}
Just replace
const weatherComponents = weatherData.items.map(weather => {
return(
<div key={weather.forecasts.area}>
<WeatherForecast
name={weather.forecasts.area}
condition={weather.forecasts.forecast}>
</WeatherForecast>
</div>
)})
return(
{weatherComponents} )
with
const weatherComponents = weatherData.items.map(weather => {
const {area, forecast} = weather.forecasts[0];
return(
<div key={area}>
<WeatherForecast
name={area}
condition={forecast}>
</WeatherForecast>
</div>
)})
return(
{weatherComponents} )