Hello I have a collapse wrapper which has its Display state as true by default (I can't change that)
And a modal component which needs the display False when it opens.
I thought that setting the defaultOpen props as "false" would set the Display to false. But it doesn' work.
What do I do wrong ?
Here is the code :
My Collapse wrapper :
import React, { Component } from "react";
import ChevronUp from "react-feather/dist/icons/chevron-up";
import ChevronDown from "react-feather/dist/icons/chevron-down";
import { Collapse } from "reactstrap";
import "./style.scss";
class CollapseWrapper extends Component {
constructor(props) {
super(props);
this.state = {
display:
props.defaultOpen === undefined ? true : props.defaultOpen,
title: this.props.title,
};
}
toggleContainer = () => {
this.setState(prevState => ({
display: !prevState.display,
}));
};
render() {
const { display, title } = this.state;
return (
<div>
<button type="button" onClick={this.toggleContainer}>
<div className="title-container">
{display ? (
<ChevronUp className="chevron" />
) : (
<ChevronDown className="chevron" />
)}
<h2>{title}</h2>
</div>
</button>
<Collapse isOpen={this.state.display}>
{this.props.children}
</Collapse>
</div>
);
}
}
export default CollapseWrapper;
My modal :
import React from "react";
import { Modal } from "reactstrap";
import CollapseWrapper from "./CollapseWrapper";
class Mymodal extends Component {
constructor(props) {
super(props);
this.state = {};
}
render() {
const { isOpen } = this.props;
return (
<Modal size="xxl" isOpen={isOpen} toggle={this.close}>
<CollapseWrapper defaultOpen="false" title="More détails">
Some content...
</CollapseWrapper>
</Modal>
);
}
}
export default Mymodal;
You should pass boolean value inside the curly braces {} not in string.
Correct defaultOpen={false}
wrong defaultOpen="false"
<CollapseWrapper defaultOpen={false} title="More détails">
Some content...
</CollapseWrapper>
use arrow function in onClick event on your button
replace
onClick={this.toggleContainer}
to
onClick={() => this.toggleContainer()}
I think, you can use component life cycle method componentWillReceiveProps(nextProps) to solve this issue. Set your display state again when componentWillReceiveProps.
Solution:
componentWillReceiveProps(nextProps) {
if (nextProps.defaultOpen !== this.state.display) {
this.setState({ ...this.state, display: nextProps.defaultOpen });
}
}
Related
I'm attempting to put data that I'm getting from an API onto a modal that will appear whenever a button is clicked.
How is this done? I'm able to use the data from the API without the modal, so I know it's not an issue with the syntax of my componentDidMount(). Not sure what the issue is and how it can be resolved.
import React from 'react';
import './App.css';
import Nav from './Nav';
import Meal from './Meal';
import meals from './Meals';
import Modal1 from './Modal'
function App() {
const mealArr = meals.map(item => <Meal food={item.food} picture={item.picture} type={item.id} />)
return (
<div className="content">
<Nav />
{mealArr}
<Modal1 isOpen={false}/>
</div>
);
}
export default App;
import React from 'react';
import Modal from 'react-modal';
class Modal1 extends React.Component {
constructor(props) {
super(props);
this.state = {
items: [],
isLoaded: false
}
}
componentDidMount() {
fetch('https://jsonplaceholder.typicode.com/users')
.then(res => res.json())
.then(json => {
this.setState({
isLoaded: true,
items: json
})
})
}
render() {
const allItems = this.state.items;
let itemArr = allItems.map(item =>
<div>
<ul>
<li key={item.id}>{item.name}</li>
</ul>
</div>)
return (
<div>
<Modal>
{itemArr}
</Modal>
</div>
)
}
}
export default Modal1;
import React, {Component} from 'react';
import Modal1 from 'react-modal';
class Meal extends Component {
constructor(props) {
super(props);
this.state = {
isOpen: false
}
this.handleClick = this.handleClick.bind(this);
this.turnOff = this.turnOff.bind(this);
}
handleClick() {
this.setState({isOpen: true})
}
turnOff() {
this.setState({isOpen: false})
}
render() {
return (
<div className="meal-container">
<h2>{this.props.type}</h2>
<h1>{this.props.food}</h1>
<img alt="" src={this.props.picture} />
<p className="steps-button" onClick={this.handleClick}>Steps</p>
<Modal1 className="modal-1" isOpen={this.state.isOpen}/>
</div>
)
}
}
export default Meal;
take a look at allItems, it's an empty array before you get the data from the API.
So, for the first render (before component did mount):
const allItems = this.state.items // ----> const allItems = []
mapping through an empty array will not produce any error and return another empty array, but when you map through an empty array, don't expect to have any item or item.name. so the itemArr is not as your expectation and cause the issue with rendering it.
to avoid from this issue, check your allItems to ensure that the data has arrived.
const allItems = this.state.items;
let itemArr = []
if (allItems.length > 0) {
itemArr = allItems.map(item => (
<div>
<ul>
<li key={item.id}>{item.name}</li>
</ul>
</div>
)
}
return (
<div>
<Modal>
{itemArr}
</Modal>
</div>
)
Edit: I have included the full code for better clarity
I am not sure if this is possible. I am trying to pass an onClick event via props but the click event does not work.
The parent component looks like this:
import React from 'react'
import { getProductsById } from '../api/get'
import Product from './Product'
import { instanceOf } from 'prop-types'
import { withCookies, Cookies } from 'react-cookie'
class Cart extends React.Component {
static propTypes = {
cookies: instanceOf(Cookies).isRequired
}
constructor(props) {
super(props)
const { cookies } = props;
this.state = {
prods: cookies.get('uircartprods') || '',
collapsed: true,
total: 0,
}
this.expand = this.expand.bind(this)
this.p = [];
}
componentDidMount() {
this.getCartProducts()
}
handleClick = (o) => {
this.p.push(o.id)
const { cookies } = this.props
cookies.set('uircartprods', this.p.toString(), { path: '/' , sameSite: true})
this.setState({prods: this.p })
console.log('click')
}
getCartProducts = async () => {
let products = []
if (this.state.prods !== '') {
const ts = this.state.prods.split(',')
for (var x = 0; x < ts.length; x++) {
var p = await getProductsById(ts[x])
var importedProducts = JSON.parse(p)
importedProducts.map(product => {
const prod = <Product key={product.id} product={product} handler={() => this.handleClick(product)} />
products.push(prod)
})
}
this.setState({products: products})
}
}
expand(event) {
this.setState({collapsed: !this.state.collapsed})
}
handleCheckout() {
console.log('checkout clicked')
}
render() {
return (
<div className={this.state.collapsed ? 'collapsed' : ''}>
<h6>Your cart</h6>
<p className={this.state.prods.length ? 'hidden' : ''}>Your cart is empty</p>
{this.state.products}
<h6>Total: {this.props.total}</h6>
<button onClick={this.handleCheckout} className={this.state.prods.length ? '' : 'hidden' }>Checkout</button>
<img src="./images/paypal.png" className="paypal" alt="Paypal" />
<a className="minify" onClick={this.expand} alt="My cart"></a>
<span className={this.state.prods.length ? 'pulse' : 'hidden'}>{this.state.prods.length}</span>
</div>
)
}
}
export default withCookies(Cart)
The Product component:
import React from 'react';
class Product extends React.Component {
constructor(props) {
super(props);
this.state = {
showDetails: false,
showModal: false,
cart: []
}
this.imgPath = './images/catalog/'
}
render() {
return (
<div className="Product">
<section>
<img src={this.imgPath + this.props.product.image} />
</section>
<section>
<div>
<h2>{this.props.product.title}</h2>
<h3>{this.props.product.artist}</h3>
<p>Product: {this.props.product.product_type}</p>
<h4>${this.props.product.price}</h4>
<button className="button"
id={this.props.product.id} onClick={this.props.handler}>Add to cart</button>
</div>
</section>
</div>
)
}
}
export default Product
If I log this.props.handler I get undefined. Everything works apart from the click handler, I was wondering if it might have something to with the async function. I am very new to React, there are still some concepts I'm not sure about, so any help is appreciated.
Okay, I see a few issues here.
First, there is no need to call this.handleClick = this.handleClick.bind(this) in the constructor, because you are using an arrow function. Arrow functions do not have a this context, and instead, accessing this inside your function will use the parent this found in the Class.
Secondly, it is wrong to store components in state. Instead, map your importedProducts inside the render function.
Thirdly, the issue with your handler is that this.props.handler doesn't actually call handleClick. You will notice in the definition handler={(product) => this.handleClick} it is returning the function to the caller, but not actually calling the function.
Try this instead.
class Product extends React.Component {
constructor(props) {
super(props);
}
render() {
return (
<div>
<button className="button" id={this.props.product.id} onClick={this.props.handler}>
Add to cart
</button>
</div>
);
}
}
export default Product;
import Product from './Product'
class Cart extends React.Component {
constructor(props) {
super(props);
}
handleClick = (o) => {
console.log('click');
};
render() {
return (
<div>
{importedProducts.map((product) => {
return <Product key={product.id} product={product} handler={() => this.handleClick(product)} />;
})}
</div>
);
}
}
export default Cart;
I have a list of ids (integer) and I have multiple components.
After a request to my API, the component receives a list of ids that should already be active.
I want to simulate a click on each element with the same id as the one in my array. I know I can use refs to do that, but I don't undertstand how to make it works with a list of elements.
Here's my code :
import React, { Component } from 'react'
import InterestBox from './InterestBox'
import Axios from 'axios'
export class InterestList extends Component {
constructor(props) {
super(props);
this.state = {pinterests: []}
}
componentDidMount() {
Axios.get('http://localhost:8000/api/interests')
.then((success) => {
this.setState({pinterests: success.data.data.interests});
})
}
componentDidUpdate(prevProps) {
console.log(JSON.stringify(prevProps));
console.log(JSON.stringify(this.props))
if(this.props.alreadyChecked != prevProps.alreadyChecked) {
this.props.alreadyChecked.forEach((item) => {
console.log(item)
})
}
}
render() {
return (
<React.Fragment>
{Object.keys(this.state.pinterests).map((interest) => {
var pinterest = this.state.pinterests[interest];
return <InterestBox id={pinterest.id} onClick={this.props.onClick} icon={pinterest.picture_src} title={pinterest.name} />
})}
</React.Fragment>
)
}
}
export default InterestList
import React, { Component } from 'react'
export class InterestBox extends Component {
constructor(props) {
super(props);
this.images = require('../../img/interests/*.svg');
this.state = {activated: false};
this.interest_box_content = React.createRef();
this.interest_text = React.createRef();
this.handleClick = this.handleClick.bind(this);
this.updateDimensions = this.updateDimensions.bind(this);
}
handleClick() {
this.props.handleClick(this.props.id, this.props.title);
this.setState(prevState => ({
activated: !prevState.activated
}))
}
updateDimensions() {
console.log((window.getComputedStyle(this.refs.interest_box_content).width))
this.refs.interest_text = (window.getComputedStyle(this.refs.interest_box_content).width)
}
render() {
return (
<div className="column is-one-fifth-desktop is-half-touch">
<div className="interest-box">
<div className="interest-box-adjuster">
<div ref={"interest_box_content"} className={"interest-box-content " + (this.state.activated == true ? 'interest-box-activated' : '')} onClick={this.handleClick}>
<img className="interest-icon" src={this.images[this.props.icon]} style={{'height': '50%'}}></img>
<i className="activated-icon fas fa-check"></i>
<span ref={"interest_text"} className="interest-text">{this.props.title}</span>
</div>
</div>
</div>
</div>
)
}
}
export default InterestBox
In the InterestList "componentDidUpdate" method, the value of the item is an integer.
I want to use this integer to "click" on the InterestBox with the corresponding "id".
How can I achieve this ?
You can store an array of elements in one ref, like this:
constructor(props) {
super(props);
this.state = {pinterests: []}
this.pinterestRefs = React.createRef()
}
...
render() {
return (
<React.Fragment>
{Object.keys(this.state.pinterests).map((interest) => {
var pinterest = this.state.pinterests[interest];
return <InterestBox id={pinterest.id} onClick={this.props.onClick} icon={pinterest.picture_src} title={pinterest.name} ref={pinterestRef => this.refs.pinterestRefs.push(pinterestRef)} />
})}
</React.Fragment>
)
}
and then call the click function on each in a componentDidMount function:
componentDidMount() {
if (this.refs.pinterestRefs.length) {
this.refs.pinterestRefs.forEach(pinterestEl => {
pinterestEl.click();
});
}
}
Since this.pinterestRefs is a ref and not an array, the push method is not available. Unfortunately, we do not have a definite length so we can't declare the refs preemptively. However, we can add it to this.refs object and the convert it to an array:
export class InterestList extends Component {
constructor(props) {
super(props);
this.state = {pinterests: []}
}
componentDidMount() {
Axios.get('http://localhost:8000/api/interests')
.then((success) => {
this.setState({pinterests: success.data.data.interests});
})
}
componentDidUpdate(prevProps) {
console.log(Object.values(this.refs)); // Array with all refs
console.log(JSON.stringify(prevProps));
console.log(JSON.stringify(this.props))
if(this.props.alreadyChecked != prevProps.alreadyChecked) {
this.props.alreadyChecked.forEach((item) => {
console.log(item)
})
}
}
render() {
return (
{/*I'm assuming each item has a unique id, if not, create one*/}
<React.Fragment>
{Object.keys(this.state.pinterests).map((interest) => {
var pinterest = this.state.pinterests[interest];
return <InterestBox id={pinterest.id} onClick={this.props.onClick} ref={pinterest.id} icon={pinterest.picture_src} title={pinterest.name} />
})}
</React.Fragment>
)
}
}
export default InterestList;
I'm trying to call a simple method from the grandparent component in my child component but from some reason I can't , I tried every possible way but I think I'm missing something
here's the full code :
import React, { Component } from 'react';
import './App.css';
var todos = [
{
title: "Example2",
completed: true
}
]
const TodoItem = (props) => {
return (
<li
className={props.completed ? "completed" : "uncompleted"}
key={props.index} onClick={props.handleChangeStatus}
>
{props.title}
</li>
);
}
class TodoList extends Component {
constructor(props) {
super(props);
}
render () {
return (
<ul>
{this.props.todosItems.map((item , index) => (
<TodoItem key={index} {...item} {...this.props} handleChangeStatus={this.props.handleChangeStatus} />
))}
</ul>
);
}
}
class App extends Component {
constructor(props) {
super(props);
this.state = {
todos ,
text :""
}
this.handleTextChange = this.handleTextChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
this.handleChangeStatus = this.handleChangeStatus(this);
}
handleTextChange(e) {
this.setState({
text: e.target.value
});
}
handleChangeStatus(){
console.log("hello");
}
handleSubmit(e) {
e.preventDefault();
const newItem = {
title : this.state.text ,
completed : false
}
this.setState((prevState) => ({
todos : prevState.todos.concat(newItem),
text : ""
}))
}
render() {
return (
<div className="App">
<h1>Todos </h1>
<div>
<form onSubmit={this.handleSubmit}>
< input type="text" onChange={this.handleTextChange} value={this.state.text}/>
</form>
</div>
<div>
<TodoList handleChangeStatus={this.handleChangeStatus} todosItems={this.state.todos} />
</div>
<button type="button">asdsadas</button>
</div>
);
}
}
export default App;
The method im trying to use is handleChangeStatus() from the App component in the TodoItem component
Thank you all for your help
This line is wrong:
this.handleChangeStatus = this.handleChangeStatus(this);
//Change to this and it works
this.handleChangeStatus = this.handleChangeStatus.bind(this);
i have an error in my react code, my result component is not changed after change array data, in console i just see new arrays after change input, new builded data is work, but component result isnot changed.
App.jsx
import React, { Component } from 'react'
import './App.css';
import fetch from 'isomorphic-fetch'
import Header from './Header'
import Content from './Content'
import Footer from './Footer'
class App extends Component {
constructor(props) {
super(props)
this.state = {
stripdata: null,
query: ""
}
this.query = this.query.bind(this)
}
componentWillMount() {
fetch(`http://localhost:3000/data/info.json`)
.then(results => results.json())
.then(data => {
this.setState({
stripdata: data
})
})
.catch(err => {
console.log("Didn't connect to API", err)
})
}
query(e) {
e = e.trim().toLowerCase();
this.setState({
query: e
})
}
show(data, query) {
return (query.length > 0) ?
data.filter(item => { return item.fakename.stripclub.toLowerCase().match( query ) }) :
data
}
render() {
console.log(this.show(this.state.stripdata, this.state.query))
return (
<div className="App">
<Header onQuery={this.query}/>
{
(this.state.stripdata === null) ?
<div className="loading">Loading data...</div> :
<Content onResult={ this.show(this.state.stripdata, this.state.query) }/>
}
<Footer />
</div>
);
}
}
export default App;
Content.jsx
import React, { Component } from 'react'
import Result from './Result'
class Content extends Component {
constructor(props) {
super(props);
this.state = {
stripdata: this.props.onResult
};
}
componentWillMount() {
}
render() {
return (
<div className="Content">
<Result stripdata={ this.state.stripdata }/>
</div>
);
}
}
export default Content;
Finder.jsx
import React, { Component } from 'react'
class Finder extends Component {
constructor(props) {
super(props)
this.state = {}
this.handleChange = this.handleChange.bind(this)
}
handleChange(e) {
this.props.find(e.target.value)
}
render() {
return (
<div className="Finder">
<form id="search" onSubmit="">
<span>i want</span>
<input type="text"
placeholder="type who you want"
onChange={ this.handleChange }
/>
</form>
</div>
);
}
}
export default Finder;
Header.jsx
import React, { Component } from 'react'
import Finder from './Finder'
class Header extends Component {
constructor(props) {
super(props);
this.state = {
};
this.find = this.find.bind(this);
}
find(e) {
this.props.onQuery(e)
}
render() {
return (
<div className="Header">
<div className="inner">
<h2>Find with which girl you want to spend your time !!!</h2>
<Finder find={ this.find }/>
</div>
</div>
);
}
}
export default Header;
Result.jsx
import React, { Component } from 'react'
import PersonCard from './PersonCard'
class Result extends Component {
constructor(props) {
super(props);
this.state = {
stripdata: this.props.stripdata
};
}
componentWillMount() {
}
render() {
return (
<div className="result">
{
this.state.stripdata.map((item, i) => {
return <PersonCard key={i} data={item}/>
})
}
</div>
);
}
}
export default Result;
import React, { Component } from 'react'
import Facebook from 'react-icons/lib/fa/facebook'
import VK from 'react-icons/lib/fa/vk'
import Phone from 'react-icons/lib/fa/phone'
PersonCard.jsx
class PersonCard extends Component {
constructor(props) {
super(props);
this.state = {
info: this.props.data
};
}
render() {
var now = new Date().getFullYear();
return (
<div className="PersonCard">
<div className="layer one">
<div className="side left face-image">
<img src={(this.state.info.photo) ? this.state.info.photo : ""} alt="Person Pic" />
</div>
<div className="side right info">
<p><b>Fake Name:</b> { this.state.info.fakename.stripclub }</p>
<p><b>Real Name:</b> { (this.state.info.name) ? this.state.info.name : null }</p>
<p><b>Currently Workplace:</b> { (this.state.info.address.work ) ? this.state.info.address.work : null }</p>
<p><b>Age:</b> { (this.state.info.born) ? (now - this.state.info.born.slice(0, 4)) : null }</p>
<p><br /><a href={ (this.state.info.fakename.facebook) ? this.state.info.fakename.facebook.id : null } ><icon><Facebook /></icon> { (this.state.info.fakename.facebook) ? this.state.info.fakename.facebook.user : null }</a></p>
<p><br /><a href={ (this.state.info.fakename.vk) ? this.state.info.fakename.vk.id : null }><icon><VK /></icon> { (this.state.info.fakename.vk) ? this.state.info.fakename.vk.user : null }</a></p>
<p><br /><a href={ (this.state.info.phone) ? "tel:" + this.state.info.phone : null }><icon><Phone /></icon> { (this.state.info.phone) ? this.state.info.phone : null }</a></p>
</div>
</div>
<div className="layer two">
<div className="about">
<p>{ this.state.info.physical }</p>
<p>{ (this.state.info.work_days) ? 'All Week ' + this.state.info.work_days : null }</p>
</div>
</div>
</div>
);
}
}
export default PersonCard;
Please help me with with this issue.
Try to write the Content component like this:
class Content extends Component {
render() {
return (
<div className="Content">
<Result stripdata={ this.props.onResult }/>
</div>
);
}
}
And the Result component like this also:
class Result extends Component {
render() {
return (
<div className="result">
{
this.props.stripdata.map((item, i) => {
return <PersonCard key={i} data={item}/>
})
}
</div>
);
}
}
And in the PersonCard replace every reference of this.state to this.props and remove the assignment in the constructor.
I am guessing the problem is with the assignments of props into state like in the Content component:
constructor(props) {
super(props);
this.state = {
stripdata: this.props.onResult
};
}
the constructor is probably not getting called after the input values changes.
The assumption that the constructor is getting called every time the component is rendered confuses a lot of beginner react devs...
Anyway you dont need to use state in these presentational components (https://medium.com/#dan_abramov/smart-and-dumb-components-7ca2f9a7c7d0)
just use props.