I receive JSON from my back end, I save it in my state and I want to use it in props in another react component, but it doesn't work.
I try to show need date like that in props of my component - {this.state.movies["0"]["title"]}, but it doesn't work.
constructor() {
super();
this.state = {
movies: []
}
}
componentDidMount() {
this.getAllMoviesForMainPage();
}
getAllMoviesForMainPage() {
axios.get("http://localhost:8080/showAll")
.then(response => {
this.setState({ movies: response.data })
})
}
render() {
return (
<div className="qwerty">
<MainPageComponent />
<div className='moviePreviewGrid'>
<Router>
<div className="moviePreviewGrid-row">
<div className="moviePreviewGrid-col">
<MoviePreview
Title={this.state.movies["2"]["title"]}
MoviePreviewAvatar={DrivePoster}
SeeMore="unrelated long string here"
/>
<NavLink to="/showByTitle/Драйв">
<button type="button" className="myBtn">See more</button>
</NavLink>
</div>
and structure of my JSON
[
{
"id": 1,
"title": "Джокер",
"releaseDate": "2019",
"genre": "Триллер, драма, криминал",
"duration": "122 минуты",
"rating": 8.7,
"criticReviews": [
{
"criticName": "Anton",
"review": "anton review"
},
{
"criticName": "OldCritic",
"review": "old review"
}
],
"userReviews": [
{
"nickName": "Igor",
"review": "igor review"
},
{
"nickName": "Nik",
"review": "nik review"
}
]
},
{
"id": 2,
"title": "Ирландец",
"releaseDate": "2019",
"genre": "Драма, триллер, криминал, биографический",
"duration": "209 минут",
"rating": 8.4,
"criticReviews": [
{
"criticName": "YoungCritic",
"review": "young review"
}
],
"userReviews": [
{
"nickName": "Gambit",
"review": "gambit review"
},
{
"nickName": "Andrew",
"review": "andrew review"
}
]
},
{
"id": 3,
"title": "Драйв",
"releaseDate": "2011",
"genre": "Боевик, драма",
"duration": "100 минут",
"rating": 7.8,
"criticReviews": [
{
"criticName": "Critic",
"review": "review"
}
],
"userReviews": [
{
"nickName": "Anton",
"review": "anton review"
}
]
},
{
"id": 4,
"title": "Последний человек на Земле",
"releaseDate": "2015",
"genre": "Комедия",
"duration": "22 минуты",
"rating": 7.3,
"criticReviews": [
{
"criticName": "NewCritic",
"review": "new review"
}
],
"userReviews": [
{
"nickName": "Atomf7",
"review": "atomf7 review"
}
]
},
{
"id": 5,
"title": "Интерстеллар",
"releaseDate": "2014",
"genre": "Фантастика, драма, приключения",
"duration": "169 минут",
"rating": 8.6,
"criticReviews": [
{
"criticName": "Nik",
"review": "nik review"
}
],
"userReviews": [
{
"nickName": "Alice",
"review": "alice review"
}
]
}
]
and i wont to have for example title of first movie
In the first init, the this.state.movies has a length of 0 so this.state.movies["2"]["title"] of course would have no value at all
because getAllMoviesForMainPage is async (axios call) and takes a little longer to complete, so first you must give it an initial value and only when the request completes you can give it the real value.
example:
<MoviePreview
Title={this.state.movies.length > 0 ? this.state.movies[2].title : ""}
MoviePreviewAvatar={DrivePoster}
/>
Usually a state, isLoading is used for this case. So you can know when you received your data.
In the first render movies are not already fetched from the api.
So you need to conditionally render it.
import React, { Component } from "react";
import axios from "axios";
export default class Test extends Component {
constructor() {
super();
this.state = {
movies: [],
loading: true
}
}
componentDidMount() {
this.getAllMoviesForMainPage();
}
getAllMoviesForMainPage() {
axios.get("http://localhost:8080/showAll")
.then(response => {
this.setState({ movies: response.data, loading: false })
})
}
render() {
const { loading, movies } = this.state;
if (loading) {
return <div>Loading...</div>;
} else {
return (
<div>
{movies.length > 0 ? (
<div>First movie title: {movies[0].title}</div>
) : (
<div>No movies</div>
)}
</div>
);
}
}
}
You can check this example using a fake api.
You are making a GET request in componentDidMount which is async so until the promise is resolve in .then(.. your state does not contain response.data in movies.
The important thing to remember is component gets re-render every time you do this.setState(.. , so before you do setState in your getAllMoviesForMainPage method, the initial render will happen and it will contain an empty array in this.state.movies that you have defined in constructor
Also since your response contains array of objects, you would be using movies['2'].title instead of movies['2']['title']
So your render method should contain something like this to show movie preview:
<div className="moviePreviewGrid-col">
{this.state.movies.length ? (
<MoviePreview
Title={this.state.movies['2'].title}
MoviePreviewAvatar={DrivePoster}
SeeMore="unrelated long string here"
/>
) : (
<PlaceHolderPreview />
)}
<NavLink to="/showByTitle/Драйв">
<button type="button" className="myBtn">
See more
</button>
</NavLink>
</div>
PlaceHolderPreview can be your another component that will show -- well a placeholder for preview.
Hope it all makes sense.
Related
I am following a tutorial and when at this point I'm getting stuck. It gives me TypeError: Cannot read property 'filter' of undefined when I try to filter through the Product array. I was implementing the same functionality using Axios according to the tutorial and it was working. The instructor then changed to redux and I followed the same steps and that's when I got the error. I'm pretty new to React and completely new to Axios and Redux and I've been trying to find out what's wrong but I'm getting nowhere.
Here's my ProductScreens.jsx where the error occurs:
import React, { useEffect } from "react";
import Rating from "../Components/rating.jsx";
import { useDispatch, useSelector } from "react-redux";
import { listProducts } from "../actions/productActions.js"
function Sale({product}){
if("salePrice" in product){
return <li>Sale Price: ₹{product.salePrice}</li>;
}
else
return null;
}
export default function ProductsScreen(props){
const dispatch = useDispatch();
const productList = useSelector( state => state.productList);
const {loading,error,products} = productList;
useEffect(() =>{
dispatch(listProducts());
},[dispatch]);
const product = products.filter(prod => prod.category === props.match.params.category && prod.subcategory === props.match.params.subcategory);
return (
<div className="container-fluid main-cards">
<div className="row">
{
product.map(product => (
<div key={product._id} className="col-12 col-md-6 col-lg-4 main-card-item">
<div className="card">
</img>
<div className="card-body product-card list-group">
<h5 className="card-title">{product.name}</h5>
<p className="card-text">{product.description}</p>
<ul className="list-unstyled mt-3 mb-4">
<li>Price: ₹{product.price}</li>
<Sale product={product}/>
<li>Buy Now</li>
<li>
<Rating rating={product.rating} numReview={product.numReview}/>
</li>
</ul>
</div>
</div>
</div>
))};
</div>
</div>
)
}
Here is my productActions.js
import Axios from "axios";
import { PRODUCT_LIST_REQUEST, PRODUCT_LIST_SUCCESS, PRODUCT_LIST_FAIL } from "../constants/productConstants"
export const listProducts = () => async (dispatch) =>{
dispatch({
type: PRODUCT_LIST_REQUEST,
});
try {
const {data} = await Axios.get('/api/products');
dispatch({type: PRODUCT_LIST_SUCCESS, payload : data});
}catch(error){
dispatch({type: PRODUCT_LIST_FAIL, payload: error.message});
}
}
Here's my productReducers.js
export const productListReducer = (state = {loading: true, products: [] }, action) =>{
switch(action.type){
case PRODUCT_LIST_REQUEST :
return {loading: true};
case PRODUCT_LIST_SUCCESS:
return {loading: false, products: action.payload};
case PRODUCT_LIST_FAIL :
return {loading: false, error: action.payload};
default:
return state;
}
}
And my productConstants.js
export const PRODUCT_LIST_REQUEST = "PRODUCT_LIST_REQUEST";
export const PRODUCT_LIST_SUCCESS = "PRODUCT_LIST_SUCCESS";
export const PRODUCT_LIST_FAIL = "PRODUCT_LIST_FAIL";
I've tried solving it but I can't find out what's wrong. I did console.log(products) instead of const product = products.filter(prod => prod.category === props.match.params.category && prod.subcategory === props.match.params.subcategory); in ProductScreens.jsx and also replaced the contents of return with <h1></h1> and got back the following object:
[
{
"_id": "1",
"name": "example1",
"category": "example category1",
"subcategory": "example subcategory1",
"image": [
{
"_id": "image1",
"name": "/images/example1.jpg"
}
],
"mainImage": "/images/example1.jpg",
"price": "19000",
"brand": "brand1",
"rating": 4.5,
"numReview": 10,
"description": "some description for example1."
},
{
"_id": "2",
"name": "example2",
"category": "example category1",
"subcategory": "example subcategory1",
"image": [
{
"_id": "image2",
"name": "/images/example2.jpg"
}
],
"mainImage": "/images/example2.jpg",
"price": "16791",
"salePrice": "15500",
"brand": "brand2",
"rating": 4.7,
"numReview": 10,
"description": "some description for example2."
},
{
"_id": "3",
"name": "example",
"category": "example category2",
"subcategory": "example subcategory3",
"image": [
{
"_id": "image3",
"name": "/images/example3-1.jpg"
},
{
"_id": "image4",
"name": "/images/example3-2.jpg"
},
{
"_id": "image5",
"name": "/images/example3-3.jpg"
},
{
"_id": "image6",
"name": "/images/example3-4.jpg"
}
],
"mainImage": "/images/example3-1.jpg",
"price": "8549",
"salePrice": "7200",
"brand": "brand3",
"rating": 3,
"numReview": 10,
"description": "some description for example3."
},
{
"_id": "4",
"name": "example4",
"category": "example category3",
"subcategory": "example subcategory4",
"image": [
{
"_id": "image7",
"name": "/images/example4.jpg"
}
],
"mainImage": "/images/example4.jpg",
"price": "450",
"brand": "brand4",
"rating": 4.5,
"numReview": 10,
"description": "some description for example4."
},
{
"_id": "5",
"name": "example5",
"category": "example category1",
"subcategory": "example subcategory2",
"image": [
{
"_id": "image8",
"name": "/images/example5.jpg"
}
],
"mainImage": "/images/example5.jpg",
"price": "30000",
"salePrice": "27000",
"brand": "brand5",
"rating": 4.5,
"numReview": 10,
"description": "some description for example5"
}
]
Looking at the object above, filter() should work but it's not. Moreover, the above object was the same object I got when I used axios and it worked fine then. So I have no idea why it's not working anymore.
Also when I try to console.log(products._id) or any other property, I get the same TypeError. I tried console.log(products[0]) thinking maybe I have to do that to get the first object since its an array of objects, I got TypeError: Cannot read property '0' of undefined.
So I figured it out. I found two solutions. First, Turns out I was getting two objects before the PRODUCT_LIST_SUCCESS action occured. In the first object, the products was an empty array from the initial state and in the second object which was receieved from PRODUCT_LIST_REQUEST action, there was only a loading property so products was undefined. So I changed the switch case statement in productsReducers.js for PRODUCT_LIST_REQUEST to return {loading: true, products: []}; and that did the trick.
My second solution is to check if loading is true or false and only execute my code and render the page if loading is false.
I haven't been an avid programmer so I don't know best programming practices but I'm using the second solution.
I have a nested JSON, the reason I fail to render is arrays in data{} are not initilaized. The same reason in this question: How do I access elements of an array in a property in Javascript object?
Now the situation is much more complex, should I do it in the same way? If so, how do I initialize this nested JSON? Or is there any smarter way?
Following is the API response:
{
"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"
}
This is my code:
import React, {Component} from 'react'
import { Grid, Header, Label } from 'semantic-ui-react'
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});
}
}
render()
{
const { data } = this.state;
return(
<div>
<Header size = 'huge'>{data.name}</Header>
<Grid container>
{
Object.entries(data).slice(2).map(
([attr, value]) => {
return(
{/*Following code does not work because:
this.state = {data:{}}
arrays inside data is not initialized,
what should I do to render every element?*/}
<Grid.Column>
<Label horizontal>{attr}</Label>
{value}
</Grid.Column>
);
}
)
}
</Grid>
</div>
);
}
}
export default raceWindow
How do I retrieve the 'name' and 'description' from the JSON data below? I have tried in my code (see below) but couldn't figure it out. Do I use an array to retrieve the name and description from the JSON data?
{
"status": {
"timestamp": "2020-10-14T09:04:56.382Z",
"error_code": 0,
"error_message": null,
"elapsed": 8,
"credit_count": 1,
"notice": null
},
"data": {
"1": {
"id": 1,
"name": "Bitcoin",
"symbol": "BTC",
"category": "coin",
"description": "Bitcoin (BTC) is a cryptocurrency . Users are able to generate BTC through the process of mining. Bitcoin has a current supply of 18,516,718. The last known price of Bitcoin is 11,440.13537699 USD and is down -0.18 over the last 24 hours. It is currently trading on 9569 active market(s) with $22,842,507,797.43 traded over the last 24 hours. More information can be found at https://bitcoin.org/.",
"slug": "bitcoin",
"logo": "https://s2.coinmarketcap.com/static/img/coins/64x64/1.png",
"subreddit": "bitcoin",
"notice": "",
"tags": [
"mineable",
"pow",
"sha-256",
"store-of-value",
"state-channels"
],
"tag-names": [
"Mineable",
"PoW",
"SHA-256",
"Store of Value",
"State channels"
],
"tag-groups": [
"OTHER",
"CONSENSUS_ALGORITHM",
"CONSENSUS_ALGORITHM",
"PROPERTY",
"PROPERTY"
],
"urls": {
"website": [
"https://bitcoin.org/"
],
"twitter": [],
"message_board": [
"https://bitcointalk.org"
],
"chat": [],
"explorer": [
"https://blockchain.coinmarketcap.com/chain/bitcoin",
"https://blockchain.info/",
"https://live.blockcypher.com/btc/",
"https://blockchair.com/bitcoin",
"https://explorer.viabtc.com/btc"
],
"reddit": [
"https://reddit.com/r/bitcoin"
],
"technical_doc": [
"https://bitcoin.org/bitcoin.pdf"
],
"source_code": [
"https://github.com/bitcoin/"
],
"announcement": []
},
"platform": null,
"date_added": "2013-04-28T00:00:00.000Z",
"twitter_username": "",
"is_hidden": 0
}
}
}
import React, { useEffect, useState } from 'react';
import Axios from 'axios'
import { Link } from 'react-router-dom';
function DetailsScreen(props){
const [cryptoVar, setcryptoList] = useState({name: "", description: ""});
useEffect(() => {
Axios.get(`http://localhost:5000/api/getProducts/${props.match.params.id}`,
{
}).then((response)=>{
console.log(response.data.data );
setcryptoList({
name: response.data.data,
description: response.data.data
});
}).catch(err =>{
console.log(err.response.data);
})
return () => {
}
}, [])
console.log(cryptoVar.description);
return(
<div>
Name: {cryptoVar.name}
Description: {cryptoVar.description}
</div>
);
}
export default DetailsScreen;
I'm trying to render an array of objects using Map and so far I've only been able to render the first item to the browser.
I figured something's up with my .map function, but I don't know enough about React and JS to pinpoint the problem.
Here's my App.js file:
// import stuff is here
class App extends Component {
constructor(props) {
super(props);
this.state = {
items: []
};
this.componentWillMount = this.componentWillMount.bind(this);
}
componentWillMount() {
fetch('THE-JSON-URL-IS-HERE')
.then(res => res.json())
.then(data => {
this.setState({ items: data });
});
render() {
const { items } = this.state;
return (
<div className="App">
{ items.map((item, num) => {
return (
<div className="people">
<div className="elem">
<p key={num}>{item.elems}</p>
</div>
<p key={num}><strong>{item.name}</strong></p>
<p key={num}><small>{item.title}</small></p>
<div className="hidden">
<p key={num}><small>{item.email}</small></p>
<p key={num}><small><strong>Office: </strong>{item.office}</small></p>
</div>
{/* <p>{item.manager}</p> */}
</div>
);
})}
</div>
);
}
}
export default App;
And here's a sample of the JSON file:
[
{
"elems": "Pr",
"name": "Abby Langdale",
"title": "President",
"email": "alangdale0#hubpages.com",
"office": "Javanrud",
"manager": [
{
"elems": "Vp",
"name": "Johnnie Mouncey",
"title": "Vice President",
"email": "jmouncey0#cnet.com",
"office": "Canto",
"manager": [
{
"elems": "Vp",
"name": "Concordia Burgwyn",
"title": "VP Quality Control",
"email": "cburgwyn0#dyndns.org",
"office": "Zhoukou",
"manager": [
{
"elems": "En",
"name": "Prissie Sainsberry",
"title": "Web Developer IV",
"email": "psainsberry0#yellowbook.com",
"office": "Tugu",
"manager": null
},
etc. Abby's info is all that I've rendered.
Since you're nesting arrays and objects into your first array element, the length of items is 1 and the only element is the Abby element with the rest of the data nested inside of it. To map through all of the elements, items should look like this array:
[
{
"elems": "Pr",
"name": "Abby Langdale",
"title": "President",
"email": "alangdale0#hubpages.com",
"office": "Javanrud",
"manager": ""
},
{
"elems": "Vp",
"name": "Johnnie Mouncey",
"title": "Vice President",
"email": "jmouncey0#cnet.com",
"office": "Canto",
"manager": ""
},
{
"elems": "Vp",
"name": "Concordia Burgwyn",
"title": "VP Quality Control",
"email": "cburgwyn0#dyndns.org",
"office": "Zhoukou",
"manager": ""
},
{
"elems": "En",
"name": "Prissie Sainsberry",
"title": "Web Developer IV",
"email": "psainsberry0#yellowbook.com",
"office": "Tugu",
"manager": null
}
]
If you need to maintain the relationship of managers, you can add an id to each object and reference it from another object.
[
{
"elems": "Pr",
"name": "Abby Langdale",
"title": "President",
"email": "alangdale0#hubpages.com",
"office": "Javanrud",
"manager": "",
"id" : 1
},
{
"elems": "Vp",
"name": "Johnnie Mouncey",
"title": "Vice President",
"email": "jmouncey0#cnet.com",
"office": "Canto",
"manager": 1
},
...
]
You would need a filter helper function to do the correct lookup for a manager's name but it should work.
Try flattening the array first. You would need to know the maximum number of levels that the array will have. Once it's flattened, you can use your map function:
const flatItems = items.flat(3); // flatten up to 3 levels
items.map((item, num) => {
return ( <render your div> );
}
I have a simple data structure that lists some information. The problem is when I click the icon it opens the both content. I didn't find a solution how to open the current div.
class HotSpots extends React.Component {
constructor(props, context) {
super(props, context);
this.handleClick = this.handleClick.bind(this);
this.state = {
isOpen: false,
icon: 'fa-plus'
}
}
handleClick(event) {
console.log(this.state.isOpen, 'clicked');
this.setState({
isOpen: !this.state.isOpen,
icon: this.state.icon === 'fa-plus' ? 'fa-remove' : 'fa-plus'
})
}
render() {
//console.log(DATA.module.hotSpots.popups);
let items = DATA.module
.hotSpots
.popups.map(n =>
<div data-open={n.openByDefault}
data-coordx={n.config.coords[0]}
data-coordy={n.config.coords[1]}
data-enable-mobile-chords={n.config.useMobileCoords>
<a style={{
marginLeft: n.config.coords[0],
marginRight: n.config.coords[1]
}}
onClick={this.handleClick}>
<i className={`fa ${this.state.icon}`}></i>
</a>
<section data-is-open={this.state.isOpen} className="hotspot-detail">
<h2>{n.headline}</h2>
<p>{n.bodyCopy}</p>
</section>
</div>
);
return (
<section className="hotspots">
{console.log('status' + this.state.isOpen)}
<div>{items}</div>
</section>
)
}
}
class App extends React.Component {
constructor(props) {
super(props)
}
render() {
return <div name={this.props.name}
data-src-phone-highres={this.props.srcPhoneHighRes}
data-src-desktop={this.props.srcDesktop}
data-src-tablet-highres={this.props.srcTabletHighres}
data-src-phone={this.props.phone}
data-src-tablet={this.props.srcTablet}
data-src-desktop-highres={this.props.srcDesktopHighres}>
<HotSpots />
</div>
}
}
React.render( <App name={DATA.moduleName}
srcPhoneHighRes={DATA.images.desktop.externalUrl}
/>, app);
http://codepen.io/agriboz/pen/MyGOMG?editors=0010
Thanks in advance
As suggested by #XtremeCoder. You're not using the unique key identifier to trace your event. So in your react component pass the key in the click handler as follow onClick={this.handleClick.bind(this, i, n) Then include a new state i which can capture the current element clicked.
this.state = {
i: 0,
isOpen: false,
}
}
handleClick(i,n) {
this.setState({
i: i,
isOpen: !this.state.isOpen,
})
Now you can check for boolean on state change to display the content and change the icon
//for icon
<i className={`fa ${(this.state.isOpen && this.state.i==i ? 'fa-close' : 'fa-plus')}` }></i>
//for content
<section data-is-open={this.state.isOpen && this.state.i==i} className="hotspot-detail">
Here is the fully functioning code
let DATA = {
"module": {
"name": "E-Hot-PDP-X9405C-X9305C-Series-en_US-2015-03-M24-fullflush",
"masterName": "E-Hot-PDP-X9405C-X9305C-Series-en_GL-2015-03-M24-fullflush",
"maintenanceStructure": "Electronics/05_Televisions_and_Home_Cinema/01_Televisions/X9405C-X9305C-Series",
"_id": "urn:sony:module:editorial_hotspots:590359239320250140028",
"isLocalized": "false",
"masterId": "urn:sony:module:editorial_hotspots:1991426988117153303078371",
"version": "20160122080005",
"readyForLive": "No",
"mode": "full",
"editorialEnabled": "yes",
"type": "urn:sony:module:editorial_hotspots",
"createdAt": 1430303942648,
"updatedAt": 1453816025859,
"dependents": [
"urn:sony:media:image_family:1995667013507913303041209"
],
"config": {
"style": "dark-text-trans-box",
"background": "true",
"grouping": "group-middle",
"type": "full",
"variation": {
"background": "white"
},
"layout": {
"name": "full",
"text": "full",
"alignment": "center",
"columns": [
"12",
"0"
]
}
},
"hotSpots": {
"popups": [
{
"headline": "Full flush surface",
"alwaysOpen": "no",
"openByDefault": "no",
"bodyCopy": "With thin styling and a full flush screen surface, you’ll enjoy an immersive viewing experience.",
"dropShadow": "no",
"bullets": [],
"links": [],
"config": {
"size": "large",
"useMobileCoords": "no",
"hotSpotsIcon": "yes",
"dropShadow": "yes",
"coords": [
"10%",
"31%"
]
}
},
{
"headline": "Wedge",
"alwaysOpen": "no",
"openByDefault": "no",
"bodyCopy": "The unique shape has been designed to increase speaker capacity for a rich, full sound.",
"dropShadow": "no",
"bullets": [],
"links": [],
"config": {
"size": "large",
"useMobileCoords": "no",
"hotSpotsIcon": "yes",
"dropShadow": "yes",
"coords": [
"78.5%",
"49%"
]
}
}
],
"image": {
"type": "urn:sony:media:image_container",
"imageFamily": {
"name": "ImgF-PDP-X9405C-X9305C-Series-2015-03-M24-fullflush",
"masterName": "ImgF-PDP-X9405C-X9305C-Series-2015-03-M24-fullflush",
"maintenanceStructure": "Electronics/05_Televisions_and_Home_Cinema/01_Televisions/X9405C-X9305C-Series",
"_id": "urn:sony:media:image_family:1995667013507913303041209",
"isLocalized": "false",
"masterId": "urn:sony:media:image_family:1995667013507913303041209",
"localizedImageFamily": {
"name": "Loc-ImgF-PDP-XBR-X9405C-X9305C-Series-2015-03-M24-fullflush-SOLA",
"masterName": "Loc-ImgF-PDP-XBR-X9405C-X9305C-Series-2015-03-M24-fullflush-SOLA",
"maintenanceStructure": "Electronics/05_Televisions_and_Home_Cinema/01_Televisions/X9405C-X9305C-Series",
"_id": "urn:sony:localized_image_family:4512059944822603303579063",
"isLocalized": "false",
"masterId": "urn:sony:localized_image_family:4512059944822603303579063",
"locale": "all",
"version": "20150327064448",
"readyForLive": "No",
"type": "urn:sony:localized_image_family",
"createdAt": 1428930904784,
"updatedAt": 1428930906576,
"localizedImageFamilies": [
{
"imageFamily": {
"_id": "urn:sony:media:image_family:4492784185533253304288460",
"isLocalized": "false",
"locale": "all",
"maintenanceStructure": "Electronics/05_Televisions_and_Home_Cinema/01_Televisions/X9405C-X9305C-Series",
"masterId": "urn:sony:media:image_family:4492784185533253304288460",
"masterName": "ImgF-PDP-XBR-X9405C-X9305C-Series-2015-03-M24-fullflush-SOLA",
"name": "ImgF-PDP-XBR-X9405C-X9305C-Series-2015-03-M24-fullflush-SOLA",
"readyForLive": "No",
"type": "urn:sony:media:image_family",
"version": "20150327061216",
"createdAt": 1428930904949,
"updatedAt": 1428930906679,
"dependents": [],
"images": {
"desktop": {
"internalUrl": "https://prod-imgws.sony.eu/cms/images/Electronics/05_Televisions_and_Home_Cinema/01_Televisions/X9405C-X9305C-Series/E-Hot-PDP-X94-X93-en_GL-2015-03-M24-fullflush_XBR-desktop.jpg",
"md5": "90e6df273a7fe6ba9ef6411a83a995e3",
"mimeType": "image/jpg",
"status": "published",
"uploadAttempts": 1,
"uploadLastAttemptAt": 1428412207508,
"assetHandle": [
"a|23360418"
]
}
}
},
"locales": [
"es_AR",
"es_BO",
"pt_BR",
"es_CL",
"es_CO",
"es_CR",
"es_DO",
"es_EC",
"es_SV",
"es_GT",
"es_HN",
"es_MX",
"es_PA",
"es_PY",
"es_PE",
"es_PR",
"es_VE",
"es_NI"
]
}
],
"dependents": [
"urn:sony:media:image_family:4492784185533253304288460"
]
},
"locale": "all",
"version": "20150327075311",
"readyForLive": "No",
"type": "urn:sony:media:image_family",
"createdAt": 1428930904791,
"updatedAt": 1453731785919,
"dependents": [
"urn:sony:localized_image_family:4512059944822603303579063"
],
"images": {
"desktop": {
"internalUrl": "https://prod-imgws.sony.eu/cms/images/Electronics/05_Televisions_and_Home_Cinema/01_Televisions/X9405C-X9305C-Series/E-Hot-PDP-X94-X93-en_GL-2015-03-M24-fullflush_desktop.jpg",
"mimeType": "image/jpg",
"uploadAttempts": 1,
"uploadLastAttemptAt": 1427203801114,
"status": "published",
"md5": "93a8c28123eede1d42d33871e6553daf",
"assetHandle": [
"a|23104311"
],
"externalUrl": "//sonyglobal.scene7.com/is/image/gwtprod/93a8c28123eede1d42d33871e6553daf?fmt=jpeg"
},
"phone": {
"internalUrl": "https://prod-imgws.sony.eu/cms/images/Electronics/05_Televisions_and_Home_Cinema/01_Televisions/X9405C-X9305C-Series/E-Hot-PDP-X94-X93-en_GL-2015-03-M24-fullflush_phone.jpg",
"mimeType": "image/jpg",
"uploadAttempts": 1,
"uploadLastAttemptAt": 1427203801114,
"status": "published",
"md5": "40df54c624e5cebfa27fdae4d3207f5d",
"assetHandle": [
"a|23104300"
],
"externalUrl": "//sonyglobal.scene7.com/is/image/gwtprod/40df54c624e5cebfa27fdae4d3207f5d?fmt=jpeg"
},
"tablet": {
"internalUrl": "https://prod-imgws.sony.eu/cms/images/Electronics/05_Televisions_and_Home_Cinema/01_Televisions/X9405C-X9305C-Series/E-Hot-PDP-X94-X93-en_GL-2015-03-M24-fullflush_tablet.jpg",
"mimeType": "image/jpg",
"uploadAttempts": 1,
"uploadLastAttemptAt": 1427203801115,
"status": "published",
"md5": "5e9ab8ac405f170a5b24f1ccf1f71c1f",
"assetHandle": [
"a|23104304"
],
"externalUrl": "//sonyglobal.scene7.com/is/image/gwtprod/5e9ab8ac405f170a5b24f1ccf1f71c1f?fmt=jpeg"
}
}
}
}
},
"locale": "en_US",
"contentLocale": "en_US"
},
"moduleId": "editorial_hotspots_590359239320250140028",
"moduleName": "editorial_hotspots"
};
class HotSpots extends React.Component {
constructor(props, context) {
super(props, context);
//this.handleClick = this.handleClick.bind(this);
this.state = {
i: 0,
isOpen: false,
}
}
handleClick(i,n) {
this.setState({
i: i,
isOpen: !this.state.isOpen,
})
}
render() {
//console.log(DATA.module.hotSpots.popups);
let items = DATA.module
.hotSpots
.popups.map((n, i) =>
<div key={i}
onClick={this.handleClick.bind(this, i, n)} data-open={n.openByDefault}
data-coordx={n.config.coords[0]}
data-coordy={n.config.coords[1]}
data-enable-mobile-chords={n.config.useMobileCoords}>
<a style={{
marginLeft: n.config.coords[0],
marginRight: n.config.coords[1]
}}
>
<i className={`fa ${(this.state.isOpen && this.state.i==i ? 'fa-close' : 'fa-plus')}` }></i>
</a>
<section data-is-open={this.state.isOpen && this.state.i==i} className="hotspot-detail">
<h2>{n.headline}</h2>
<p>{n.bodyCopy}</p>
</section>
</div>
);
return (
<section className="hotspots">
{console.log('status' + this.state.isOpen)}
<div>{items}</div>
</section>
)
}
}
class App extends React.Component {
constructor(props) {
super(props)
}
render() {
return <div className={`${DATA.module.config.variation.background} ${DATA.module.config.type}`}
name={this.props.name}
data-src-desktop={this.props.srcDesktop}
data-src-phone={this.props.phone}
data-src-tablet={this.props.srcTablet}>
<HotSpots />
</div>
}
}
React.render( <App name={DATA.moduleName}
srcDesktop={DATA.module.hotSpots.image.imageFamily.images.desktop.externalUrl}
srcPhone={DATA.module.hotSpots.image.imageFamily.images.phone.externalUrl}
srcTablet={DATA.module.hotSpots.image.imageFamily.images.tablet.externalUrl}
/>, app);
Codepen:
http://codepen.io/anon/pen/WwJPLe?editors=0010
Hope it helps!
This is because you are associating same state(isOpen) to all the contents.
To show this modified your pen to this:
handleClick(event) {
this.setState({
parentClass :event.currentTarget.className,
isOpen: !this.state.isOpen,
icon: this.state.icon === 'fa-plus' ? 'fa-remove' : 'fa-plus'
})
}
render() {
let items = DATA.module
.hotSpots
.popups.map((n,i) =>
<div data-open={n.openByDefault}
data-coordx={n.config.coords[0]}
data-coordy={n.config.coords[1]}
data-enable-mobile-chords={n.config.useMobileCoords}>
<a style={{
marginLeft: n.config.coords[0],
marginRight: n.config.coords[1]
}}
onClick={this.handleClick} className={'parent'+i}>
<i className={`fa ${this.state.icon}`}></i>
</a>
<section data-is-open={this.state.isOpen && (this.state.parentClass===("parent"+i)) } className="hotspot-detail">
<h2>{n.headline}</h2>
<p>{n.bodyCopy}</p>
</section>
</div>
);
return (
<section className="hotspots">
{console.log('status' + this.state.isOpen)}
<div>{items}</div>
</section>
)
}
}
http://codepen.io/Debabrata89/pen/bpMOOM?editors=0010
Hope this helps