How to access function from different components React - reactjs

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.

Related

Handle multiple child component in React

I've tried to look everywhere and couldn't find anything related to my use case, probably I'm looking for the wrong terms.
I have a situation where I have a bar with 3 icons, I'm looking for set one icon "active" by changing the class of it.
The icon is a custom component which have the following code
export default class Icon extends Component {
state = {
selected : false,
}
setSelected = () => {
this.setState({
selected : true
})
}
setUnselected = () => {
this.setState({
selected : false
})
}
render() {
var classStatus = '';
if(this.state.selected)
classStatus = "selected-icon"
else
classStatus = "icon"
return <div className={classStatus} onClick={this.props.onClick}><FontAwesomeIcon icon={this.props.icon} /></div>
}
}
In my parent component I have the following code
export default class MainPage extends Component {
handleClick(element) {
console.log(element);
alert("Hello!");
}
render() {
return (
<div className="wrapper">
<div className="page-header">
<span className="menu-voice">File</span>
<span className="menu-voice">Modifica</span>
<span className="menu-voice">Selezione</span>
</div>
<div className="page-main">
<span className="icon-section">
<div className="top-icon">
<Icon icon={faFileCode} onClick={() => this.handleClick(this)} />
<Icon icon={faCodeBranch} onClick={() => this.handleClick(this)} />
<Icon icon={faMagnifyingGlass} onClick={() => this.handleClick(this)} />
</div>
</span>
<span className="files-section">Files</span>
<span className="editor-section"></span>
</div>
<div className="page-footer">
Footer
</div>
</div>
);
}
}
What I'm trying to achieve is that when one of the Icon child component get clicked it will set the selected state to true manage by the parent component, in the same time while one of them is true I would like that the parent would set to false the other twos.
I've tried to use the useRef function but it doesn't look as a best practise.
Which is the correct way to do it? Sending also this to the handleClick function it just return the MainPage class instead of the child. Any suggestion at least where I should watch?
Thanks in advance
I suggest not storing the state in the icon, since it doesn't know what else you're using it for. Simply have the icon component take it's 'selected' status from props. e.g.
export default class Icon extends Component {
render() {
var classStatus = '';
if(this.props.selected)
classStatus = "selected-icon"
else
classStatus = "icon"
return (
<div className={classStatus} onClick={this.props.onClick}>.
<FontAwesomeIcon icon={this.props.icon} />
</div>
);
}
};
Then you can just manage the state in the parent where it should be:
export default class MainPage extends Component {
constructor(props) {
super(props);
this.state = { selectedOption : '' };
}
handleSelectOption(newValue) {
this.setState({ selectedOption: newValue });
}
isSelected(value) {
return value === this.state.selectedOption;
}
render() {
return (
<div className="wrapper">
{ /* etc... */ }
<div className="page-main">
<span className="icon-section">
<div className="top-icon">
<Icon
icon={faFileCode}
onClick={() => this.handleSelectOption("File")}
selected={isSelected("File")}
/>
<Icon
icon={faCodeBranch}
onClick={() => this.handleSelectOption("Modifica")}
selected={isSelected("Modifica")}
/>
{ /* etc... */ }
</div>
</span>
</div>
{ /* etc... */ }
</div>
);
}
};
You should define a constructor in your class component:
constructor(props) {
super(props);
this.state = { selected : false };
}
You also have to call a function which modify the state when you click on the Icon. onClick={this.props.onClick} doesn't change the state

React, get data from Firebase and update State

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!

How to pass an user-created array to another component?

I am new to React. In fact I am new to any frontend programming lanugage. Therefore I encounter many really weird and sometimes even hilarious problems. I am struggling with sending an array to another compontent. The problem is user creates that array, and it's created dynamically inside render(){return(..)}
class Home extends Component {
constructor(props) {
super(props)
this.chosenItems = [];
}
state = {
items: [],
};
// code to import JSON from backend API, irrelevant, that part works fine
addItem(item){
this.chosenItems.push(item);
console.log(this.chosenItems); //logs created array, everything works like a charm
}
render() {
const {items} = this.state;
return (
//some code
<div key={item.id}>
{item.name} {item.price}<img src = {item.url} className="photo"/><button onClick={() => this.addItem(item)}>ADD</button>
</div>
<Basket dataFromParent = {this.getItems} />
</div>
and Basket class
class Basket extends React.Component {
constructor(props) {
super(props);
this.chosenItems = [];
}
state = {
items: []
};
componentDidUpdate()
{
this.chosenItems = this.props.dataFromParent;
console.log(this.props.dataFromParent);
}
render() {
return (
<div>
<h2>{this.chosenItems}</h2>
<h2>{this.props.dataFromParent}</h2>
</div>
);
}
}
export default Basket;
the problem is console log shows "undefined". Could you tell me what I am doing wrong? Or maybe my entire approach is incorrect and I should look for another solution?
Update
class Home extends Component {
state = {
items: [],
chosenItems []
};
// code to import JSON from backend API, irrelevant, that part works fine
addItem(item){
this.setState(prev => ({
chosenItems: [...prev.chosenItems, item]
}))
}
render() {
const {items, chosenItems} = this.state;
return (
<div>
<div><Basket chosenItems ={this.state.chosenItems} /></div>
<Router>
<div className="container">
<ul>
<Link to="/login">login</Link>
<Link to="/basket">basket</Link>
</ul>
<Route path="/login" component={Login} />
<Route path="/basket" component={Basket} />
</div>
</Router>
<div>
{items.map(item =>
<div key={item.id}>
{item.name} {item.price} {item.quantity} <img src = {item.url} className="photo"/><button onClick={() => this.addItem(item)}>Add!</button>
</div>
)}
</div>
</div>
);
}
}
class Basket extends React.Component {
render() {
return (
<div>
{this.props.chosenItems.map(item =>
<div key={item.id}>
{item.name}{item.price}
</div>
)}
</div>
);
}
}
and that works, but the chosenItems array is printed immediatelty where
<Basket chosenItems ={this.state.chosenItems} />
is located after the button is pressed. And when I click on basket redirection I get
TypeError: Cannot read property 'map' of undefined
Firstly, you must understand that things that you don't set in state don't cause a re-render and hence an updated data isn't reflected on to the UI or passed onto the children.
Secondly, you do not need to store the data passed from parent in child again, you can directly use it from props
class Home extends Component {
state = {
items: [],
chosenItems []
};
// code to import JSON from backend API, irrelevant, that part works fine
addItem(item){
this.setState(prev => ({
chosenItems: [...prev.chosenItems, item]
}))
}
render() {
const {items} = this.state;
return (
<div>
//some code
<div key={item.id}>
{item.name} {item.price}<img src = {item.url} className="photo"/><button onClick={() => this.addItem(item)}>ADD</button>
</div>
<Basket chosenItems ={this.state.chosenItems} />
/div>
)
}
}
class Basket extends React.Component {
render() {
return (
<div>
<h2>{this.props.chosenItems.map(item=> <div>{item.name}</div>)}</h2>
</div>
);
}
}
export default Basket;
I see a couple of problems in the snippet -
You are passing this.getItems to your child component as props. It's never defined in the parent. I think it should have been items array state that you have created.
chosenItems should have been a state and you should dig deeper on how to update a state. There is a setState function, learn abt it.
In child, again the the constructor is written like parent's with chosenItems and items which is not needed. You can use them from props.
Please have a look on https://reactjs.org/docs/react-component.html#setstate how to mutate the state of a component. You will find few more basics over this document.
The reason you are getting undefined in the log is because the this.getItems() in Home component is returning undefined either there is no such method or probably the state variable itself is undefined.
In a nutshell few things:
When you want to pass an array to child component, it is as simple as passing any object or property For eg. (I am hoping you want to pass the choosen items to Basket component)
Always initialise state in constructor.
so chooseItems and items should be a part of state and inside constructor.
Your code should look like:
class Home extends Component {
constructor(props) {
super(props)
this.state = {
chosenItems: [],
items: [],
}
}
addItem(item){
this.setState({
chosenItems: [...this.state.chosenItems, item]
})
console.log(this.chosenItems); //logs created array, everything works like a charm
}
render() {
const {items, chooseItems} = this.state;
return (
items.map(item => {
return (
<div key={item.id}>
{item.name} {item.price}<img src = {item.url} className="photo"/>
<button onClick={() => this.addItem(item)}>ADD</button>
</div>
)
})
<Basket dataFromParent={chooseItems} />
div>
)
}
}
and the Basket component would not need constructor since the required data is coming from parent component:
class Basket extends React.Component {
render() {
return (
<div>
{this.props.dataFromParent.map(item => <h2>{this.props.dataFromParent}</h2>)}
</div>
);
}
}
export default Basket;

React - Make an FAQ class toggle on click

I am creating an FAQ with React and I have the questions in strong tags and the answers in p tags. On click of the strong tags I would like to add a class of active to the clicked tag. I am close but there is some sort of scope issue on my toggle function and I'm not sure how to move past it:
import React, { Component } from "react";
import "./faq.css";
class Questions extends Component {
constructor(props) {
super(props);
this.state = {
active: false
};
}
toggleClass() {
const currentState = this.state.active;
this.setState({ active: !currentState });
}
render() {
let faq = [
{
question: "Lorem",
answer: "Ipsum"
},
{
question: "Dolor",
answer: "Sit"
}
];
return (
<div className="questions">
{faq.map((item, index) => {
return (
<div className="item">
<strong
className={this.state.active ? "active" : null}
onClick={this.toggleClass}
>
{item.question}
</strong>
<p>{item.answer}</p>
</div>
);
})}
</div>
);
}
}
export default Questions;
Here's what I have so far
You need to bind toogleClass method to the Questions instance:
Opition one: Using bind
import React, { Component } from "react";
import "./faq.css";
class Questions extends Component {
constructor(props) {
super(props);
this.state = {
active: false
};
this.toggleClass.bind(this)
}
toggleClass() {
const currentState = this.state.active;
this.setState({ active: !currentState });
}
render() {
let faq = [
{
question: "Lorem",
answer: "Ipsum"
},
{
question: "Dolor",
answer: "Sit"
}
];
return (
<div className="questions">
{faq.map((item, index) => {
return (
<div className="item">
<strong
className={this.state.active ? "active" : null}
onClick={this.toggleClass}
>
{item.question}
</strong>
<p>{item.answer}</p>
</div>
);
})}
</div>
);
}
}
export default Questions;
Option 2: Using class property initializer
import React, { Component } from "react";
import "./faq.css";
class Questions extends Component {
constructor(props) {
super(props);
this.state = {
active: false
};
this.toggleClass.bind(this)
}
toggleClass = () => {
const currentState = this.state.active;
this.setState({ active: !currentState });
}
render() {
let faq = [
{
question: "Lorem",
answer: "Ipsum"
},
{
question: "Dolor",
answer: "Sit"
}
];
return (
<div className="questions">
{faq.map((item, index) => {
return (
<div className="item">
<strong
className={this.state.active ? "active" : null}
onClick={this.toggleClass}
>
{item.question}
</strong>
<p>{item.answer}</p>
</div>
);
})}
</div>
);
}
}
export default Questions;
To selected only the active question you can do this:
import React, { Component } from "react";
import "./faq.css";
class Questions extends Component {
constructor(props) {
super(props);
this.state = {
activeQuestion: null
};
}
render() {
let faq = [
{
question: "Lorem",
answer: "Ipsum"
},
{
question: "Dolor",
answer: "Sit"
}
];
return (
<div className="questions">
{faq.map((item, index) => {
return (
<div className="item">
<strong
data-question={item.question}
className={
item.question === this.state.activeQuestion ? "active" : null
}
onClick={() => {
console.log(item.question);
this.setState({ activeQuestion: item.question });
}}
>
{item.question}
</strong>
<p>{item.answer}</p>
</div>
);
})}
</div>
);
}
}
export default Questions;
Your function toggleClass needs to be an arrow function -> toggleClass = () => {...your code here...}. When it's a regular function the outer scope (where this.state is), is not being passed to your function. Without an arrow function, when you refer to this you are referring only to the scope of the toggleClass function, where state does not exist and so is undefined.
Working Code
Also due to setState being async, it's best practice to use current state by referencing it within the setState function like below:
toggleClass = () => {
this.setState(prevState => ({
active: !prevState.active
})
}
When you reference state outside of the setState function then pass it in, it's possible that the state would be different by the time you use it to set it.
i.e. say the currentState you got was true, and then your setState uses current state to set the opposite (False) by the time setState tries to set it, something else might have changed your existing state to False already and you are just setting it to False again (instead of modifying what the current state is at that time which would be False and you want want True). Unlikely in your case, but it is good practice to follow this because you might run into this issue elsewhere

Updating one value in an object of arrays

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.

Resources