I have an array containing objects with details for an image (URL, description, likes) I'm trying to clone an Instagram page, and update the "likes" for that 1 image on click.
Attempted to map through the array and return with the "likes" + 1.
Here are 3 separate files starting with the data. The data is stored in the "Main" section in the state Gallery. So to overview, I want to increase the number of likes when I click on that image. But when I setState, I have no idea how I can only target one value in one object of the array. I would rather just update the state rather than create a new state onClick and then change the value that was! I'm looking for the best practice. (as this is the only way I can learn) Thanks in advance.
const images =[
{
url:'./images/img1.jpg',
description:"test1",
likes:0,
index:0
},
{
url:'./images/img2.jpg',
description:"test1",
likes:3,
index:1
},
{
url:'./images/img3.jpg',
description:"test1",
likes:4,
index:2
},
{
url:'./images/img2.jpg',
description:"test1"
},
{
url:'./images/img2.jpg',
description:"test1"
},
{
url:'./images/img2.jpg',
description:"test1"
},
]
export default images
import React from 'react'
const Gallery =(props)=>{
return (
<div className="container">
<div className="main-gallery">
{props.gallery.map((item,index) => (
<div key={index} className='img-container' onClick= {props.increaseLikes}>
<img className='gallery-images' src={item.url}/>
<p className='likes'>likes {item.likes}</p>
</div>
))}
</div>
</div>
)
}
export default Gallery
import React, { Component } from "react";
import ReactDOM from 'react-dom';
import Nav from './Components/Navbar/Nav'
import Header from './Components/Header/Header'
import Carousel from './Components/Carousel/Carousel'
import Data from './Data'
import Gallery from './Components/Gallery/Gallery'
class Main extends Component {
constructor(props) {
super(props);
this.state={
post:100,
gallery:[],
}
}
componentDidMount(){
this.setState({
gallery:Data
})
}
increaseLikes=()=>{
//no idea how to update
}
render() {
return (
<div>
<Gallery gallery={this.state.gallery} increaseLikes= {this.increaseLikes}/>
</div>
)
}
}
export default Main;
Your increaseLikes function needs to get id of the image from the Gallery component.
So the code must be like something like this:
I assumed your data has an unique id property.
increaseLikes = id => {
const updatedData = this.state.gallery.map(image => {
if (image.id === id) {
return { ...image, likes: image.likes ? image.likes + 1 : 1 };
} else {
return image;
}
});
this.setState({
gallery: updatedData
})
};
Gallery component code:
import React from "react";
const Gallery = props => {
return (
<div className="container">
<div className="main-gallery">
{props.gallery.map((item, index) => (
<div
key={item.id}
className="img-container"
onClick={() => props.increaseLikes(item.id)}
>
<img
className="gallery-images"
src={item.url}
alt={item.description}
/>
<p className="likes">likes {item.likes ? item.likes : 0} </p>
<hr />
</div>
))}
</div>
</div>
);
};
export default Gallery;
you could use the url (That seems to be the only unique value) of the images in order to update your array, I've made a StackBlitz where you can see how to do it. Hope this helps.
Related
Here's the code for Panel
`
import React from "react";
// import {render} from "react-dom";
import AddInventory from "components/AddInventory";
class Panel extends React.Component{
constructor(props) {
super(props);
this.state = {
activeIndex: ''
}
}
componentDidMount() {
this.activePanel();
}
closePanel=()=>{
this.setState({
activeIndex : false
})
}
activePanel = ()=>{
this.setState({
activeIndex : true
})
}
render(){
return(
<div>
{/*<button className={"button is-primary add-btn"} onClick={this.activePanel}>add</button>*/}
<div className={this.state.activeIndex ? 'panel-wrapper active':'panel-wrapper'}>
<div className={"over-layer"}>
<div className={"panel"}>
<div className={"head"}>
<span onClick={this.closePanel} className={"close"}>x</span>
<AddInventory></AddInventory>
</div>
</div>
</div>
</div>
</div>
)
}
}
export default Panel;
Products:
import React from "react";
import ToolBox from "components/ToolBox";
import Product from "components/Product";
import axios from 'components/axios'
import {CSSTransition , TransitionGroup} from 'react-transition-group'
import Panel from "components/Panel";
class Products extends React.Component{
product =[];
source =[];
state ={
product : [{
id:'1',
name:'Air Jordan1',
tags:'45 colours',
image:'images/1.jpg',
price:'21000',
status:'available'
},
{
id:'2',
name:'Nike Pual George PG 3',
tags:'45 colours',
image:'images/2.jpg',
price:'11000',
status:'available'
},
{
id:'3',
name:'Jordan Why Not Zer0.2',
tags:'10 colours',
image:'images/3.jpg',
price:'15000',
status:'unavailable'
},
]
}
componentDidMount() {
// fetch('http://localhost:3003/products').then(response => response.json()).then( data=>{
// console.log(data)
// this.setState({
// product : data
// })
// })
axios.get('/products').then(response => {
this.setState( {
product : response.data,
source : response.data
})
})
}
search = text=>{
//1.get a new array from product
let _product = [...this.state.source]
//2.filter the array
let res = _product.filter((element)=>{
return element.name.toLowerCase().includes(text.toLowerCase())
})
//set state
this.setState({
product : res
})
}
add = ()=>{
let panel = new Panel(this.props)
panel.activePanel()
}
// add =()=>{
// panel.setState({
// activeIndex : true
// })
// }
render() {
return(
<div>
<ToolBox search={this.search}/>
<div className={'products'}>
<div className="columns is-multiline is-desktop">
<TransitionGroup component={null}>
{
this.state.product.map(p=>{
return (
<CSSTransition
timeout={400}
classNames="product-fade"
key={p.id}
>
<div className="column is-3" key={p.id}>
<Product product={p}/>
</div>
</CSSTransition>
)
})
}</TransitionGroup>
{/*<div className="column is-3">*/}
{/* <Product/>*/}
{/*</div>*/}
{/*<div className="column is-3">*/}
{/* <Product/>*/}
{/*</div>*/}
</div>
<button className={"button is-primary add-btn"} onClick={this.add}></button>
</div>
</div>
)
}
}
export default Products;
I was trynna use activePanel() in Products but it gives me : Warning: Can't call setState on a component that is not yet mounted. This is a no-op, but it might indicate a bug in your application. Instead, assign tothis.statedirectly or define astate = {};` class property with the desired state in the Panel component.
I tried initialize a new panel() but it still gives me the same error.
welcome. I don't think this approach is best practice. Generally, components should only ever be updating their own state (see here) and typically you want data to flow from parent component to child component (see here). Additionally, your design is deceptive. When you render a component, you declare it as JSX in some render (or return) statement. But here, Panel is never formally instantiated in JSX.
In Panel, I would suggest watching a prop such as active via shouldComponentUpdate and updating state based on changes to that prop. Then in Products you can instantiate an instance of Panel in JSX and dynamically set the value of that prop.
I've been creating some React Apps using local data to populate Components etc and all is working fine. Im now at the point where i want to host this data externally (Firebase) and consume it within my apps.
My issue may be a general 'Working With React and External Data' kinda thing but in a basic way...
I get my data from Firebase
When component has mounted
I use setState to pass this external data to the component state
I expect my components to rerender with this new external data
Unfortunately my page isnt updating with the new Firebase data, it just uses the Initial state i am setting
I wondered if using componentWillMount() was a better area to assign the Firebase data so it is ready before the first render but it seems this approach is now deprecated
Is there something obviously wrong with the way i am getting, setting or passing my Firebase data from one Parent component to a Child component?
Thanks
Parent component
import './App.scss';
import DataThoughts from "./assets/data/DataThoughts";
import Thoughts from "./components/Thoughts";
import HideHeader from './components/HideHeader';
import HideFooter from './components/HideFooter';
import FireBaseThoughts from './components/firebaseThoughts';
import {onValue, ref} from "firebase/database";
import {Component} from "react";
class AppThoughts extends Component {
constructor(props) {
super(props);
this.state = {
"data": [
{
"Date": "Initial Date",
"Thought": "Initial Thought",
"Author": "Initial Author",
"Location": "Initial Location",
"Photo": "Initial Photo",
"Emotion": "Initial Emotion"
}
]};
};
componentDidMount() {
onValue(ref(FireBaseThoughts, '/'), (snapshot) => {
const data = snapshot.val();
this.setState({data});
});
}
render() {
const data = this.state.data;
return (
<div className="App">
<HideHeader />
<HideFooter />
<main className={'bg-light'}>
<div className={'container-fluid'}>
<Thoughts data={data} />
</div>
</main>
</div>
);
}
}
export default AppThoughts;
Child component
import React, { Component } from 'react';
import FilterButton from "./FilterButton";
class Thoughts extends Component {
constructor(props) {
super(props);
this.state = {...props};
}
handleClick = value => () => {
// set to initial data
this.setState({ ...this.props });
if(value === "All") {
// Do nothing
} else {
// filter the initial data
let filtered = this.props.data.filter(item => item.Emotion === value);
this.setState({ data: filtered });
}
};
render() {
let data = this.state.data;
let numberOfThoughts = data.length;
let dataList = this.state.data.map((thought, i) =>
<div key={'thought'+i} className={`card thought mb-5 ${thought.Photo ? '' : 'bg-transparent shadow-0'} animate__animated animate__fadeIn`} style={{animationDelay:`${(i / 10)}s`}}>
{thought.Photo ? <img src={thought.Photo} className="card-img-top" alt={thought.Emotion}/> : ''}
<div className={`${thought.Photo ? 'p-5' : 'p-5'}`}>
<blockquote className={`blockquote mb-0 ${thought.Photo ? '' : 'text-danger'}`}>
<p className={'small opacity-50'}>{thought.Date}</p>
<p className={`${thought.Photo ? 'display-6' : ' display-4'} mb-4`}>{thought.Thought}</p>
<footer className="small opacity-50">{thought.Author}, <cite title="Source Title">{thought.Location}</cite></footer>
</blockquote>
</div>
</div>
);
return (
<section className="row section-row justify-content-start thoughts py-5">
<div className={'card-columns'}>
{dataList}
</div>
<div className={'appControlsInfo'}>
<span className={'appControlsInfo__items'}>{numberOfThoughts}</span>
<button type="button" className="btn btn-danger appControlsInfo__btn" data-bs-toggle="collapse" data-bs-target="#collapseExample" aria-expanded="false" aria-controls="collapseExample">
<i className="bi bi-list"></i>
</button>
<div className={'appControlsInfo__more collapse'} id="collapseExample">
<FilterButton buttonText={"All"} buttonType={'btn-dark'} onClick={this.handleClick('All')} />
<FilterButton buttonText={"Happy"} buttonType={'btn-dark'} onClick={this.handleClick('Happy')} />
<FilterButton buttonText={"Sad"} buttonType={'btn-dark'} onClick={this.handleClick('Sad')} />
<FilterButton buttonText={"Thinking"} buttonType={'btn-dark'} onClick={this.handleClick('Thinking')} />
</div>
</div>
</section>
);
}
}
Thoughts.defaultProps = {
Photo: '',
Emotion:'Emotion',
Date:'Date',
Thought:'Thought',
Author:'Author',
Location:'Location'
};
export default Thoughts; // Don’t forget to use export default!
I'm new to React btw, I've been trying to read my test.json file which is located in :
src/data/test.json
My app.js is written as such :
import React, { Component } from 'react';
import './App.css';
var HotelData = require('../data/test.json')
class App extends Component {
constructor(){
super();
this.state = {
data: [],
}
}
getDatafromHotel(){
fetch(HotelData)
.then((Response) => Response.json())
.then((findresponse) => {
console.log(findresponse.data);
this.setState({data: findresponse.data})
})
}
componentDidMount(){
this.getDatafromHotel();
}
render() {
return(
<div className="body">
<div className="container">
{this.state.HotelData.map((hotel) => (
<div className="row">
<div className="col-1">
{hotel.roomTypeLabel}
</div>
<div className="col-2">
</div>
</div>
))}
</div>
</div>
);
}
}
export default App;
Is there any way that I could read test.json file rather than the index.html in the public folder?
test.json file : https://api.myjson.com/bins/16ocrc
You can't use require to import JSON files like you would for javascript ones. Take a look at the fetch api: https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API/Using_Fetch.
As for the code you need to change, it would be:
getDatafromHotel(){
fetch("../data/test.json")
.then((Response) => Response.json())
.then((findresponse) => {
console.log(findresponse.data);
this.setState({data: findresponse.data})
})
Lastly, you need to remove the require statement.
the line:
var HotelData = require('../data/test.json';
should be:
var HotelData = '../data/test.json';
assuming that location is correct and not a 404.
I have started working with React a month ago. Now I am building a step by step application. I have my code, but I have the feeling that I can clean it up even more.
Can someone check this code.. and give me advice which from I can learn and improve my React skills?
My Code works but wondering if this can be cleaner:
import React, { Component } from 'react';
import axios from 'axios';
class App extends Component {
state = {
fragrances: []
}
componentDidMount() {
const URI = 'http://localhost:1337/';
const post_type = 'fragrances';
axios
.get(`${URI + post_type}`)
.then(response => {
const fragrances = response.data;
this.setState({ fragrances });
})
.catch(error => {
console.log('An error occurred:', error);
});
}
render() {
const { fragrances } = this.state;
return (
<React.Fragment>
<div className="row">
{
fragrances.map((fragrance, index) => {
const url = 'http://localhost:1337';
const image = fragrance.Image;
const name = fragrance.Name;
const category = fragrance.Category;
const desc = fragrance.Description;
return (
<div key={index} className="col-sm-6 col-xs-12">
<div className="fragrance">
{ image ? <img className="fragrance__image" src={url + image.url} alt={name} /> : <h4>Geen foto beschikbaar.</h4>}
{ name ? <h2 className="fragrance__title">{name}</h2> : 'Geen titel aanwezig.'}
{ category ? <span class="fragrance__category">{category}</span> : ' '}
{ desc ? <p className="fragrance__description">{desc}</p> : 'Geen omschrijving aanwezig.'}
</div>
</div>
)
})
}
</div>
</React.Fragment>
)
}
}
export default App;
Thanks in advance.
I'd recommend you to preform the 'Reformat code'. As far as I know most Intellij IDE's have this, which can be preformed by pressing: CTRL + ALT + L.
This together with a (strict) linter (ESLint) can result in a clear overall structure and thus increased readability.
Creating a <Fragrance /> component would make it more clean plus using ES6 deconstruction. Also the Fragment is unnecessary around the list as it is being wrapped in a single <div className="row">:
....
render() {
const { fragrances } = this.state;
const url = 'http://localhost:1337';
return (
<div className="row">
{fragrances.map((fragrance, index) => {
const { Image: image, Name: name, Category: category, Description: desc } = fragrance;
return (
<Fragrance
key={index}
image={image}
name={name}
category={category}
desc={desc}
/>
)
})
}
</div>
)
}
...
Fragrance.js
const Fragance = ({name, category, image, desc }) => (
<div key={index} className="col-sm-6 col-xs-12">
<div className="fragrance">
{ image ? <img className="fragrance__image" src={url + image.url} alt={name} /> : <h4>Geen foto beschikbaar.</h4>}
{ name ? <h2 className="fragrance__title">{name}</h2> : 'Geen titel aanwezig.'}
{ category ? <span class="fragrance__category">{category}</span> : ' '}
{ desc ? <p className="fragrance__description">{desc}</p> : 'Geen omschrijving aanwezig.'}
</div>
</div>
)
export default Fragrance;
Move API logic to separate module.
Move fragrance description to separate component.
Consider using HOC for getting data and functional component to render it.
Use prettier to format your code. https://prettier.io
If it’s a real application, consider using state management lib like Redux.
Your Code Observation:
Code inside componentDidMount() can be moved to some central place, like some separate component which exports these, all axios code resides there.
Always try to break component in smaller and manageable pieces, which improves readability and also makes it more manageable.
Format the code in such a way that all your code lies in the viewable area, we shouldn't scroll horizontally to see the running code.
Your code inside <div className="fragrance"> can be improved on readability, like below, try to always make it clean, this way it better to read.
const { fragrances } = this.state;
const image = <img className="fragrance__image" src={url + image.url} alt={name} />;
const name = <h2 className="fragrance__title">{name}</h2>;
const category = <span class="fragrance__category">{category}</span>;
const desc = <p className="fragrance__description">{desc}</p>;
return (
<React.Fragment>
<div className="row">
{
fragrances.map((fragrance, index) => {
const url = 'http://localhost:1337';
const image = fragrance.Image;
const name = fragrance.Name;
const category = fragrance.Category;
const desc = fragrance.Description;
return (
<div key={index} className="col-sm-6 col-xs-12">
<div className="fragrance">
{ image ? {image} : <h4>Geen foto beschikbaar.</h4>}
{ name ? {name} : 'Geen titel aanwezig.'}
{ category ? {category} : ' '}
{ desc ? {desc} : 'Geen omschrijving aanwezig.'}
</div>
</div>
)
})
}
</div>
</React.Fragment>
)
Please don't use index in map has Key , it will lead to unpredictable apps, please read this article index-as-a-key-is-an-anti-pattern
Consider using HOC ( Higher Order Components ) React HOC
Use CSS Modules to avoid CSS clashes CSS Modules
// conatnsts.js
export const URI = 'http://localhost:1337/';
export const URL = 'http://localhost:1337';
export const POST_TYPE = 'fragrances';
// App.js
import React, { Component } from 'react';
import axios from 'axios';
import { URI, URL, POST_TYPE } from './constants.js';
class App extends Component {
state = {
fragrances: [],
};
componentDidMount() {
axios
.get(`${URI + post_type}`)
.then(response => {
const fragrances = response.data;
this.setState({ fragrances });
})
.catch(error => {
console.log('An error occurred:', error);
});
}
render() {
const { fragrances } = this.state;
return (
<>
<div className="row">
{
fragrances.map(({ Image: image, Name: name, Category: category, Description: desc}, index) => (
<div key={index} className="col-sm-6 col-xs-12">
<div className="fragrance">
{image ? <img className="fragrance__image" src={URL + image.url} alt={name} /> : <h4>Geen foto beschikbaar.</h4>}
{name ? <h2 className="fragrance__title">{name}</h2> : 'Geen titel aanwezig.'}
{category ? <span class="fragrance__category">{category}</span> : ' '}
{desc ? <p className="fragrance__description">{desc}</p> : 'Geen omschrijving aanwezig.'}
</div>
</div>)
)
}
</div>
</>
)
}
}
export default App;
I added slight changes, but to keep your code clean and more readable in React write component only for the purpose that it should serve. Your component shouldn't think about how to render image or category or description. Instead you should have a component smth like FragranceListItem. This component(App component) is the FragranceList. So your FragranceList component renders FragranceListItems. In a FragranceListItem you can have FragranceImage, FragranceName, FragranceCategory, FragranceDescription components, which will get needed info by the props and they will decide how to show their data if condition is true or not. Think about components like functions. Function should be simple, it should do only one thing, the same React component.
And what about fetching data and placing it in a state. If this is a small applications, that this works. But if it will get bigger, the need of libraries such as redux, react-redux, redux-thunks, redux-sagas will appear. These libraries have their approaches how to keep your application state, how to make serverside calls from separate modules and hold side-effects far from your components.
I'm trying to fetch data for a React component and set it as a nested object in state:
import React from 'react';
import './App.css';
import XLSX from "xlsx";
class App extends React.Component {
constructor(props){
super(props);
this.state = {
isLoading:false,
cards:{}
}
}
componentDidMount(){
this.setState({isLoading:true});
/* convert xlsx to JSON */
fetch("../data/Cards_v0.1.xlsx")
.then((d)=> d.arrayBuffer()).then((d)=>{
const data = new Uint8Array(d);
const workbook = XLSX.read(data, {type:"buffer"});
const sheet = workbook.Sheets["Deck"];
const cardsJSON = XLSX.utils.sheet_to_json(sheet,{range:1});
let cards = {};
for(let i=0; i<cardsJSON.length; i++){
for(let j=0; j<cardsJSON.length; j++){
cards[cardsJSON[i].Name] = cardsJSON[i];
}
}
this.setState({cards:cards, isLoading:false});
});
}
render() {
if(this.state.isLoading){
return <div>Loading</div>;
}
return (
<div>
{ this.state.cards.Single.Name }
</div>
);
}
}
export default App;
React devtools shows that the object is in state, with cards>Single>Name being "Single", but {this.state.cards.Single.Name} throws TypeError: Cannot read property 'Name' of undefined.
What's confusing me most is that {this.state.cards.Single} instead throws Objects are not valid as a React child (found: object with keys {Name, Type, Rarity, Text, Money}). If you meant to render a collection of children, use an array instead.
So the key Name is found when it's not being called, but then the object becomes undefined when I call it?
Very confused. Any help is appreciated!
React doesn't know how to display objects, therefore, {this.state.cards.Single} will throw Objects are not valid as a React child.
You also have some odd choice of setting React state. Since the component is always going to fetch data on mount, it makes more sense to make isLoading to be defaulted to true, then set to false on successful fetch response.
I don't know how your cardsJSON is structured, but the example below shows two ways to display nested JSON.
Wrapping it in pre, code html elements and using JSON.stringify(obj, replacer, spaces)
Destructing the object properties from this.state.cards (if any of these are properties that are also nested objects, then they'll also need to be destructed as well!) and then displaying all destructed data in a table, list, etc.
Working example: https://codesandbox.io/s/km3wwvqqzv
Example.js
import React, { Component, Fragment } from "react";
import DisplayCode from "./displayCode";
import DisplayList from "./displayList";
export default class Example extends Component {
state = {
isLoading: true,
cards: {}
};
componentDidMount = () => {
fetch("https://jsonplaceholder.typicode.com/users")
.then(response => response.json())
.then(json => this.setState({ cards: json, isLoading: false }));
};
render = () =>
this.state.isLoading ? (
<div>Loading</div>
) : (
<Fragment>
<DisplayCode cards={this.state.cards} />
<DisplayList cards={this.state.cards} />
</Fragment>
);
}
displayCode.js
import React, { Fragment } from "react";
export default ({ cards }) => (
<Fragment>
<h3>Display JSON as code:</h3>
<pre style={{ height: 300, overflowY: "auto" }}>
<code>{JSON.stringify(cards, null, 4)}</code>
</pre>
</Fragment>
);
displayList.js
import map from "lodash/map";
import React, { Fragment } from "react";
export default ({ cards }) => (
<Fragment>
<h3 style={{ marginTop: 30 }}>Display JSON as list:</h3>
<ul style={{ height: 300, overflowY: "auto" }}>
{map(
cards,
({
id,
name,
username,
email,
address: {
street,
suite,
city,
zipcode,
geo: { lat, lng }
}
}) => (
<li key={id}>
<strong>id:</strong> {id}
<ul>
<li>
<strong>Username:</strong> {username}
</li>
<li>
<strong>Name:</strong> {name}
</li>
<li>
<strong>Email:</strong> {email}
</li>
<li>
<strong>Street: </strong>
{street}
</li>
<li>
<strong>Suite: </strong>
{suite}
</li>
<li>
<strong>City: </strong>
{city}
</li>
<li>
<strong>Zipcode: </strong>
{zipcode}
</li>
<li>
<strong>Lat: </strong>
{lat}
</li>
<li>
<strong>Lng: </strong>
{lng}
</li>
</ul>
</li>
)
)}
</ul>
</Fragment>
);