Attribute ref return null in React - javascript

Trying to get a reference to a DOM element in react is returning null, I have used selector methods and ref yet all returned null. The code those work well, as the dropdown is created, but the problem is:
while i click on the document or the button for the second time, i get an error message saying this.containerElem is null.
Any help will be appreciated.
This is my code
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
class DropdownMenu extends React.Component {
constructor() {
super();
this.state = {
isToggle: false,
};
}
handleClick(e) {
e.preventDefault();
this.setState({
isToggle: !this.state.isToggle,
}, () => document.addEventListener('click', this.closeMenu.bind(this))
);
}
closeMenu(ev) {
if (!this.containerElem.contains(ev.target)) {
this.setState({
isToggle: !this.state.isToggle
}, () => document.removeEventListener('click', this.closeMenu))
}
}
render() {
return (
<div>
<ClickBtn onHandleClick={this.handleClick.bind(this)} />
{
(this.state.isToggle) ? (
<div className="menu"
ref={(node) => {
this.containerElem = node;
}}
>
<button>Menu Item 1</button>
<button>Menu Item 1</button>
<button>Menu Item 1</button>
</div>
) :
(
null
)
}
</div>
)
}
}
function ClickBtn(props) {
return (
<button type="button" onClick={props.onHandleClick}>Click to Toggle</button>
)
}
ReactDOM.render(
<DropdownMenu />, document.getElementById('root')
);

Related

React onClick event in array.map()

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;

react does not update DOM

import React, { Component } from "react";
import ReactDOM from "react-dom";
import "./index.css";
class App extends Component {
constructor(props) {
super(props);
this.state = {
text: "",
listItem: []
}
this.onChangeInput = this.onChangeInput.bind(this);
this.addToList = this.addToList.bind(this);
this.keyPress = this.keyPress.bind(this);
}
onChangeInput(event) {
this.setState({
text: event.target.value
});
}
addToList () {
let list = this.state.listItem;
list.push(this.state.text);
this.setState({
text: ""
});
this.setState({
listItem: list
});
}
deleteItem(event) {
console.log(event.target.remove());
}
keyPress (e) {
if (e.key === "Enter") {
this.addToList()
}
}
render() {
const listItem = this.state.listItem;
const list = listItem.map((val, i) =>
<li key={i.toString()} onClick={this.deleteItem}>
{val}
</li>
);
console.log(list);
return (
<div className="container">
<Input onChange={this.onChangeInput} value={this.state.text}
keyPress={this.keyPress}
/>
<Button addToList={this.addToList}/>
<ul>
{list}
</ul>
</div>
);
}
}
class Input extends Component {
render() {
return <input type="text" className="input" onChange={this.props.onChange}
onKeyPress={this.props.keyPress}
value={this.props.value}/>;
}
}
class Button extends Component {
render() {
return (
<button className="button" onClick={this.props.addToList}>
Add To List
</button>
);
}
}
ReactDOM.render(<App />, document.getElementById("root"));
I'm very confused and couldn't find solution any where.
I'm new to react.
when I delete the list items, I delete them from DOM but is in state and I didn't delete it from state.
I put console.log(list) in render method and on every key press logs list in console
my question is why DOM does not re-render lists and output those where deleted from DOM and not from state?
and why it works for new list items and ignore those that deleted from DOM ?
react dosent pickup the update in the way you are doing it
deleteItem(event) {
console.log(event.target.remove());
}
although the item will be removed , but react dosent have any clue that happend, to notify react that the items has changed and it need to re-render, you need to call setState , then react calls the render method again,
deleteItem(e) {
const list= this.state.listItem;
list.pop() // remove the last element
this.setState({
list: list
});
}

How to pass props in my components inReactJS

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 });
}
}

Why doesn't react component render when trying to render it in another components click function

I'm trying to render buttons to a page which when clicked render the hard-coded weather data to the page depending on the day that was clicked. The click function works fine and the buttons are rendered just as I expect them to, but when a button is clicked the Day component doesn't render.
I don't understand what I'm doing wrong since my code reaches the console.log in the click handler function. Then the handler function should render the component but for some reason it does not.
Here is my code:
import React from "react";
import ReactDOM from "react-dom";
import "./index.css";
import myData from "./weather.json";
class Day extends React.Component {
constructor(props) {
super(props);
this.state = {
description: null
};
}
render() {
console.log("at last"); //this isn't reached
return (
<div className="dayWeather">
<div className="dayWeather">Humidity {this.props.humidity}</div>
<div className="dayWeather">Temperature {this.props.temperature}</div>
</div>
);
}
}
class DayRow extends React.Component {
constructor(props) {
super(props);
this.state = {
days: Array(7).fill(null)
};
this.handler = this.handler.bind(this);
this.renderDay = this.renderDay.bind(this);
}
handler(day) {
let daysWeather = myData[day];
console.log("now we've reached this far"); //this console log is reached when a button is clicked.
return (
<Day
humidity={daysWeather.humidity}
temperature={daysWeather.temperature}
/>
);
}
renderDay(day) {
return (
<div>
<button
className="day"
onClick={() => {
this.handler(day);
}}
>
{day}
</button>
</div>
);
}
render() {
return (
<div>
<div className="day-row">
{this.renderDay("Monday")}
{this.renderDay("Tuesday")}
{this.renderDay("Wednesday")}
{this.renderDay("Thursday")}
{this.renderDay("Friday")}
{this.renderDay("Saturday")}
{this.renderDay("Sunday")}
</div>
</div>
);
}
}
class Weather extends React.Component {
render() {
return (
<div className="weather">
<div className="weather-panel">
<DayRow />
</div>
<div className="day" />
</div>
);
}
}
// ========================================
ReactDOM.render(<Weather />, document.getElementById("root"));
Don't return UI elements in click handler as it won't render. Just set a flag inside handler and use it to display the Day component inside your render function.
class DayRow extends React.Component {
constructor(props) {
super(props);
this.state = {
days: Array(7).fill(null),
showDay: false
};
this.handler = this.handler.bind(this);
this.renderDay = this.renderDay.bind(this);
}
handler() {
this.setState({
showDay: true,
});
}
renderDayComponent(day) {
let daysWeather = myData[day];
console.log("now we've reached this far"); //this console log is reached when a button is clicked.
return (
<Day
humidity={daysWeather.humidity}
temperature={daysWeather.temperature}
/>
);
}
renderDay(day) {
return (
<div>
<button
className="day"
onClick={() => {
this.handler();
}}
>
{day}
</button>
{this.state.showDay && this.renderDayComponent(day)}
</div>
);
}
render() {
return (
<div>
<div className="day-row">
{this.renderDay("Monday")}
{this.renderDay("Tuesday")}
{this.renderDay("Wednesday")}
{this.renderDay("Thursday")}
{this.renderDay("Friday")}
{this.renderDay("Saturday")}
{this.renderDay("Sunday")}
</div>
</div>
);
}
}
My guess is that you are trying to render the day in the Weather compoment? To do this, you must keep some sort of state so that React knows what to render. Whenever you change the state, React will call render to re-render your compoment.
Therefore, since the state of wether or not a day is to be shown is local to the Weather component, you need to store the state there:
class Weather extends React.Component {
constructor(props) {
super(props)
this.state = { activeDay: undefined }
}
render() {
// Store the current dayData in a variable if an activeDay is chosen, it will be falsy if no day has been chosen.
const dayData = this.state.activeDay && myData[this.state.activeDay]
return (
<div className="weather">
<div className="weather-panel">
// DayRow will inform Weather when a day has been selected, i.e., clicked
<DayRow onSelected={day => this.setState({ activeDay: day })} />
</div>
<div className="day">
// This is where you are rendering the day, only if a day
// is active. I.e., dayData is truthy
{ dayData && <Day humitidy={dayData.humitidy} temperature={dayData.temperature} /> }
</div>
</div>
);
}
}
Your DayRow would simple communicate with Weather by saying which day is selected.
class DayRow extends React.Component {
constructor(props) {
super(props);
this.renderDay = this.renderDay.bind(this)
}
renderDay(day) {
return (
<div>
<button
className="day"
onClick={() => {
this.props.onSelected(day);
}}
>
{day}
</button>
</div>
);
}
render() {
return (
<div>
<div className="day-row">
{this.renderDay("Monday")}
{this.renderDay("Tuesday")}
{this.renderDay("Wednesday")}
{this.renderDay("Thursday")}
{this.renderDay("Friday")}
{this.renderDay("Saturday")}
{this.renderDay("Sunday")}
</div>
</div>
);
}
}
Returning JSX from your event handler will not render it. You have to do all rendering in your component's render method.
You could instead have an additional object in your state that keep track of if the day has been clicked or not, and use that state in your rendering.
class DayRow extends React.Component {
constructor(props) {
super(props);
this.state = {
days: Array(7).fill(null),
showDays: {}
};
this.handler = this.handler.bind(this);
this.renderDay = this.renderDay.bind(this);
}
handler(day) {
this.setState(previousState => {
const showDays = { ...previousState.showDays };
showDays[day] = !showDays[day];
return { showDays };
});
}
renderDay(day) {
let daysWeather = myData[day];
return (
<div>
<button
className="day"
onClick={() => {
this.handler(day);
}}
>
{day}
</button>
{this.state.showDays[day] && (
<Day
humidity={daysWeather.humidity}
temperature={daysWeather.temperature}
/>
)}
</div>
);
}
// ...
}
The returned component from handler function is being passed to onClick event. Its not getting into the DOM tree.
You can change the code as shown below.
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import myData from './weather.json';
const myData =
class Day extends React.Component {
constructor(props) {
super(props);
this.state = {
description: null
};
}
render() {
console.log('at last'); //this isn't reached
return (
<div className="dayWeather">
<div className="dayWeather">Humidity {this.props.humidity}</div>
<div className="dayWeather">Temperature {this.props.temperature}</div>
</div>
);
}
}
class DayRow extends React.Component {
constructor(props) {
super(props);
this.state = {
days: Array(7).fill(null)
};
this.handler = this.handler.bind(this);
this.renderDay = this.renderDay.bind(this);
}
handler(day) {
let daysWeather = myData[day];
console.log('now we\'ve reached this far'); //this console log is reached when a button is clicked.
this.props.handler(daysWeather);
}
renderDay(day) {
return (
<div>
<button
className="day"
onClick={() => {
this.handler(day);
}}
>
{day}
</button>
</div>
);
}
render() {
return (
<div>
<div className="day-row">
{this.renderDay('Monday')}
{this.renderDay('Tuesday')}
{this.renderDay('Wednesday')}
{this.renderDay('Thursday')}
{this.renderDay('Friday')}
{this.renderDay('Saturday')}
{this.renderDay('Sunday')}
</div>
</div>
);
}
}
class Weather extends React.Component {
constructor(props){
super(props);
this.state = {
selectedDay: {}
};
this.handler = this.handler.bind(this);
}
handler(day){
this.setState({
selectedDay: day
});
}
render() {
let day = null;
if(Object.keys(this.state.selectedDay) > 0){
day = <Day
humidity={selectedDay.humidity}
temperature={selectedDay.temperature}
/>;
}
return (
<div className="weather">
<div className="weather-panel">
<DayRow onDayChange={this.handler}/>
</div>
<div className="day">
{day}
</div>
</div>
);
}
}
// ========================================
ReactDOM.render(<Weather />, document.getElementById('root'));

calling grandparent method from grandchild functional component in react

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);

Categories

Resources