Conditionally render elements in React JS? - javascript

I have a child component which i am looping over array to print out title and values. I have an event listener which renders a new row of title and values. I have a button in child component which i want do not want to be displayed by default but rendered only when i add new rows. So every 2 rows, there will be one button, for every 3, there will be 2 and so on.
This is my app.js file
import React, {Component} from 'react';
import './App.css';
import Child from './Child.js'
class App extends Component {
state = {
myArray: [
{ title: 'Hello', value: 'hello' }
]
}
addNewField = () => {
const myArray = [...this.state.myArray, { title: 'Hello', value: 'hello' }]
this.setState ({
myArray
})
}
render() {
return (
<div className = "App">
{
this.state.myArray.map ( (val,idx) => {
return (
<Child
key = {idx}
title = {val.title}
animal = { val.value }
/>
)
})
}
<button onClick = {this.addNewField}>Add Me</button>
</div>
)
}
}
export default App;
This is the setup for my Child Component:-
import React from 'react'
const Child = (props) => {
return (
<div>
<h3>{props.title}</h3>
<h4>{props.value}</h4>
<button>New Block!!</button>
</div>
)
}
export default Child
So basically the button in the Child component named new block should not be displayed by default but only after every click there after. Thank you.

Add a prop to the parent with the index of the map loop. Then add a flag so only children rendered after the first get the "New Block!!" button:
render() {
return (
<div className = "App">
{
this.state.myArray.map ( (val,idx) => {
return (
<Child
key = {idx}
title = {val.title}
animal = { val.value }
renderIndex = {idx}
/>
)
})
}
<button onClick = {this.addNewField}>Add Me</button>
</div>
)
}
}
import React from 'react'
const Child = (props) => {
return (
<div>
<h3>{props.title}</h3>
<h4>{props.value}</h4>
{props.renderIndex > 0 && <button>New Block!!</button>}
</div>
)
}
export default Child

Related

React component that's rendered dynamically does not rerender on parent state changes

I have a component that I want to run through a non react animation library before render. This has prevented me from going the standard route of just using the standard hide/show logic. I initially tried to use ReactDOM.createPortal but that didn't render the component at all. Using ReactDOM.render, I've gotten the element to render correctly upon completion of the animation and I'm able to successfully propagate changes up to the "parent" state but the state change doesn't propagate back down to the "child". Here's my code:
Html
<div id="root"></div>
<div id="childPlaceholder"></div>
Javascript
import './App.css';
import React, { useEffect, useState } from 'react';
import ReactDOM from 'react-dom';
function App() {
const [data, updateData] = useState(0)
function add(val) {
console.log("add");
updateData(val);
}
function renderSubpage() {
let $el = document.getElementById("childPlaceholder");
// NonReactAnimationLibrary.ShowContainer($el);
ReactDOM.render(<Child number={data} add={add} />, $el);
// ReactDOM.createPortal(<Child number={data} add={add} />, $el);
}
return ( <>
<button onClick={renderSubpage}>
add child
</button>
<div> data: {data}</div>
</>
);
}
function Child(props) {
return <>
<button onClick={()=>{props.add(props.number + 1)}}>add number</button>
<div>child {props.number}</div>
</>
}
export default App;
Is it possible to do this in react?
Update 1:
So I've updated the code per Olivers response, it renders correctly using the portal but the child components still don't rerender on state changes in the Parent Component
const root = document.getElementById("root");
const childRoot = document.getElementById("childPlaceholder");
function Child(args) {
return ReactDOM.createPortal(<>
<div>child: {args.number}</div>
<button onClick={()=>{args.add(args.number+1)}}>Increment base number</button>
</>, childRoot);
}
export default class App extends React.Component {
constructor() {
super();
this.state = { data: 0, number:0 };
}
add = (val)=> {
this.setState({
...this.state,
number: val
});
}
addChild = () => {
this.setState(prevState => ({data: prevState.data + 1}));
}
render() {
const children = Array(this.state.data)
.fill()
.map((_, i) => <Child key={i} number={0} add={this.add}/>);
return (
<div>
<button onClick={this.addChild}>
add child
</button>
<div> data: {this.state.data}</div>
{children}
</div>
);
}
}
ReactDOM.render(<App/>, root);
Update 2:
The culprit was found. Changed
number={0}
to
number={this.state.number}
and it works
React.createPortal must be used inside the render method (I used a class component because I cannot use hooks in the SO example, you can of course use a functional component).
You can use it in the App component like below or in the Child component :
const root = document.getElementById("root");
const childRoot = document.getElementById("childPlaceholder");
function Child({number}) {
return <div>child {number}</div>;
}
class App extends React.Component {
constructor() {
super();
this.state = { data: 0 };
}
addChild = () => {
this.setState(prevState => ({data: prevState.data + 1}));
}
render() {
const children = Array(this.state.data)
.fill()
.map((_, i) => <Child key={i} number={i} />);
return (
<div>
<button onClick={this.addChild}>add child</button>
<div> data: {this.state.data}</div>
{ReactDOM.createPortal(children, childRoot)}
</div>
);
}
}
ReactDOM.render(<App/>, root);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<div id="root"></div>
<div id="childPlaceholder"></div>

I want to click a parent component to render a child component in react

I want to click on an option in the menu. This options should then display all the items associated with that option in the child component. I know I am going wrong in two places. Firstly, I am going wrong in the onClick function. Secondly, I am not sure how to display all the items of ONLY the option (Eg.Brand, Holiday destination, Film) that is clicked in the child component. Any help would be greatly appreciated.
horizantalscroll.js
import React from 'react';
import ScrollMenu from 'react-horizontal-scrolling-menu';
import './hrizontalscroll.css';
import Items from './items';
// list of items
// One item component
// selected prop will be passed
const MenuItem = ({ text, selected }) => {
return (
<div
className="menu-item"
>
{text}
</div>
);
};
onclick(){
this.onClick(name)}
}
// All items component
// Important! add unique key
export const Menu = (list) => list.map(el => {
const { name } = el;
return (
<MenuItem
text={name}
key={name}
onclick={this.onclick.name}
/>
);
});
const Arrow = ({ text, className }) => {
return (
<div
className={className}
>{text}</div>
);
};
const ArrowLeft = Arrow({ text: '<', className: 'arrow-prev' });
const ArrowRight = Arrow({ text: '>', className: 'arrow-next' });
class HorizantScroller extends React.Component {
state = {
selected: 0,
statelist: [
{name: "Brands",
items: ["1", "2", "3"]
},
{name: "Films",
items: ["f1", "f2", "f3"]
},
{name: "Holiday Destination",
items: ["f1", "f2", "f3"]
}
]
};
onSelect = key => {
this.setState({ selected: key });
}
render() {
const { selected } = this.state;
// Create menu from items
const menu = Menu(this.state.statelist, selected);
const {statelist} = this.state;
return (
<div className="HorizantScroller">
<ScrollMenu
data={menu}
arrowLeft={ArrowLeft}
arrowRight={ArrowRight}
selected={selected}
onSelect={this.onSelect}
/>
<items items={items}/>
</div>
);
}
}
export default HorizantScroller;
items.js
import React, { Component } from "react";
import HorizontalScroller from "horizontalscroll.js";
class Items extends React.Component{
render() {
return (
<div>
{this.statelist.items.map({items, name}) =>
name === this.statelist.name && <div>{items}</div>
}
</div>
);
}
}
export default Items;
The answer is in passing to your Items component the correct array it needs to show. You are already creating a state variable for what's selected. So it would be along the lines of:
<Items items={items[selected]}/>

Why the UI component of the react is not getting updated?

It's more of a conceptual problem rather than a bug fix.
I am fairly new to react and was trying to build a simple todo application with add and remove function. I implemented a feature that whenever the todo list element would be clicked it would be removed. I styled it with CSS and border it.
Main issue here is, whenever i click the element of the list,it goes away, but the border remains. Just the text dissapper not the whole div.
Here is the App.js code
import React, { Component } from 'react';
import './App.css';
import Navbar from './Navbar/Navbar'
import InputBox from './inputBox/inputBox';
import ListTodo from './listTodo/listTodo';
class App extends Component {
constructor(props){
super(props)
console.log("This is from the constuor");
this.state={
todo:[
],
temp:''
}
}
changed=(thing)=>{
var x = thing.target.value;
this.setState({
temp:x
})
}
addTodo = ()=>{
const item = [
...this.state.todo
]
item.push(this.state.temp);
this.setState({
todo:item
})
}
removeIt = (index)=>{
const item = [
...this.state.todo
]
delete item[index]
this.setState({
todo:item
})
}
render() {
return (
<div className="change">
<Navbar/>
<InputBox
changed={(event)=>this.changed(event)}
addTodo = {this.addTodo}
/>
<ListTodo
todoList = {this.state.todo}
removeIt = {this.removeIt}
/>
</div>
);
}
}
export default App;
My inputBox component:
import React from 'react';
import {Button} from 'react-bootstrap';
import './inputBox.css'
const inputBox = (props)=>{
const keyPress=(e)=>{
if(e.keyCode === 13){
console.log('value', e.target.value);
props.addTodo();
}
}
return(
<div className="center">
<input className="inputDis" type="text" key='1' onChange={props.changed} onKeyDown={keyPress}/>
<Button className="buttonDis" bsStyle="info" key='2' onClick={props.addTodo}>Info</Button>
</div>
)
}
export default inputBox
My todo list component:
import React from 'react';
import {Button} from 'react-bootstrap';
import './listTodo.css'
const displayTodo = (props)=>{
const items = props.todoList.map((item, index)=>{
return(
<div key={index+"upper"} onClick={()=>props.removeIt(index)} className="listTodo">
<span key={index+"div"}>{item}</span>
{/* <Button onClick={()=>props.removeIt(index)} bsStyle="info" key='remove'>X</Button> */}
</div>
)})
return (
<div>{items}</div>
);
}
export default displayTodo
I really cant understand how to get this thing work.Here is the UI image
Your removeIt code is the issue, you copy all elements, then delete the value at a specific index, leaving it undefined (a hole), so it get's rendered by todoList.map...
removeIt = (index)=>{
const item = [
...this.state.todo
]
delete item[index]
this.setState({
todo:item
})
}
The common convention is to filter all the elements whose index is not equal to the one you're trying to remove
removeIt = (index)=>{
const newItems = this.state.items.filter((el, elIndex) => elIndex !== index);
this.setState({
todo: newItems
})
}
const data = [1,2,3,4,5,6];
// delete method
const data1 = [...data];
delete data1[3]; // delete index 3
console.log(data1); // [1,2,3,undefined,5,6]
// filter method
const data2 = [...data].filter((el, index) => index !==3); // filter index 3 out
console.log(data2); // [1,2,3,5,6]

React JS - How to access props in a child component which is passed from a parent component

In my react application, I am passing my data from parent to child as props. In my child component, I am able to see the data in props however when I try to access the data, I am getting an error saying "cannot read property of undefined".
I have written my child component like below-
Child Component-
import React from 'react';
import ReactDOM from 'react-dom';
import { setData } from '../actions/action'
import { connect } from 'react-redux'
import {
Accordion,
AccordionItem,
AccordionItemTitle,
AccordionItemBody,
} from 'react-accessible-accordion';
import 'react-accessible-accordion/dist/fancy-example.css';
import 'react-accessible-accordion/dist/minimal-example.css';
const ChildAccordion = (props) => {
console.log(props);
return (
<Accordion>
<AccordionItem>
<AccordionItemTitle>
<h3> Details:
{ props?
props.map(d =>{
return <span>{d.key}</span>
})
:
""
}
</h3>
<div>With a bit of description</div>
</AccordionItemTitle>
<AccordionItemBody>
<p>Body content</p>
</AccordionItemBody>
</AccordionItem>
</Accordion>
)
};
export default ChildAccordion
Parent Component-
import React from 'react';
import ReactDOM from 'react-dom';
import ChildAccordion from './ChildAccordion'
import { setData } from '../actions/action'
import { connect } from 'react-redux'
import {
Accordion,
AccordionItem,
AccordionItemTitle,
AccordionItemBody,
} from 'react-accessible-accordion';
import 'react-accessible-accordion/dist/fancy-example.css';
import 'react-accessible-accordion/dist/minimal-example.css';
class ParentAccordion extends React.Component {
componentWillMount() {
//call to action
this.props.setData();
}
getMappedData = (dataProp) =>{
if (dataProp) {
let Data = this.props.dataProp.map(d =>{
console.log(d);
})
}
}
render(){
const { dataProp } = this.props;
return (
// RENDER THE COMPONENT
<Accordion>
<AccordionItem>
<AccordionItemTitle>
<h3>Policy Owner Details:
{ dataProp?
dataProp.map(d =>{
return <span>{d.key1}</span>
})
:
""
}
</h3>
</AccordionItemTitle>
<AccordionItemBody>
<ChildAccordion {...dataProp} />
</AccordionItemBody>
</AccordionItem>
</Accordion>
);
}
}
const mapStateToProps = state => {
return {
dataProp: state.dataProp
}
};
const mapDispatchToProps = dispatch => ({
setData(data) {
dispatch(setData(data));
}
})
export default connect (mapStateToProps,mapDispatchToProps) (ParentAccordion)
I am using map function inside as my api response can be array of multiple objects.
Once you know what the prop that you're passing in is called, you can access it like so from within your child component: {props.data.map(item => <span>{item.something}</span>}
const Parent = () => {
return (
<Child data={[{ id: 1, name: 'Jim' }, { id: 2, name: 'Jane ' }]} />
);
}
const Child = (props) => {
return (
<ul>
{props.data.map(item => <li key={item.id}>{item.name}</li>)}
</ul>
);
}
You are passing dataProp down to ChilAccordian as a prop. So in Child component you should access it using props.dataProp and do map on props.dataProp but not on props directly
ChildAccordian:
<h3> Details:
{ Array.isArray(props.dataProp) && props.dataProp.length > 0 ?
props.dataProp.map(d =>{
return <span key={d.id}>{d.key}</span>
})
:
""
}
</h3>
Also keep in mind that you have to add unique key to parent Jsx element when you generate them in loop like for loop, .map, .forEach, Object.keys, OBject.entries, Object.values etc like I did in the above example. If you don’t get unique id from the data then consider adding index as unique like
<h3> Details:
{ Array.isArray(props.dataProp) && props.dataProp.length > 0 ?
props.dataProp.map((d, index) =>{
return <span key={"Key-"+index}>{d.key}</span>
})
:
""
}
</h3>
Edit: If it is an object then do something like below and regarding using a method to generate jsx elements
getMappedData = dataProp =>{
if(props.dataProp){
Object.keys(props.dataProp).map(key =>{
return <span key={"Key-"+key}>{props.dataProp[key]}</span>
});
}else{
return "";
}
}
<h3> Details:
{this.getMappedData(props.dataProp)}
</h3>

setState() doesn't update value

I'm trying to update state of my component and it's not working. I have Main page container component => nested Navbar container component => nested NavItem UI component. Also on Main page I have AddNavItem container component which should add item to Navbar. It looks like this:
Main
|
|--NavBar
| |
| |--NavItem
|
|--AddNavItem
This is my Main page code:
import React, { Component } from 'react';
import Navbar from '../nav/Navbar'
import AddNavItem from '../fields/AddNavItem'
class Main extends Component {
state = {
items: [
{id: 1, name: 'Услуги', link: 'services'},
{id: 2, name: 'Цены', link: 'prices'},
{id: 3, name: 'Как это работает?', link: 'works'},
]
}
addNavItem = (item) => {
this.setState((state) => {
let newItems = [...state.items];
newItems.unshift(item)
console.log(newItems);
return {
items: newItems
}
});
}
render() {
return (
<div>
<Navbar items={ this.state.items }/>
<AddNavItem addNavItem={ this.addNavItem }/>
</div>
);
}
}
export default Main;
The problem is that I always get old array with 3 initial obj even after addNavItem is firing. <Navbar /> still gets array with 3 element. I have read about async setState and makes as describe in doc https://reactjs.org/docs/faq-state.html What have I doing wrong?
UPD: I changed code (unshift) to return array, not a length of the array. Console log shows me that I get new array with 4 obj
UPD: My complete code for all components:
Navbar
import React, { Component } from 'react';
import NavItem from './NavItem';
class Navbar extends Component {
state = {
items: this.props.items,
}
render() {
return (
<div className="centered navbar grey">
<h2>MyReact</h2>
<ul>
<NavItem items={ this.state.items }/>
</ul>
</div>
);
};
}
export default Navbar;
NavItem:
import React from 'react';
const NavItem = ({ items }) => {
const menuItemList = items.map((item) => {
return (
<li key={item.id}>
{ item.name }
</li>
);
})
return (
<div>
{ menuItemList }
</div>
)
}
export default NavItem;
AddNavItem:
import React, { Component } from 'react';
class AddNavItem extends Component {
state = {
id: Math.random(),
name: null,
link: null
}
handleInput = (e) => {
this.setState({
[e.target.id]: e.target.value,
});
}
handleSubmit = (e) => {
e.preventDefault();
this.props.addNavItem(this.state);
}
render() {
return (
<div className="centered flex-column-centered">
<form onSubmit={ this.handleSubmit }>
<h4 className="labelField">Название раздела:</h4>
<input
className="inputField"
type="text"
id="name"
placeholder="укажите название"
onChange={ this.handleInput } />
<h4 className="labelField">URL:</h4>
<input
className="inputField"
type="text"
id="link"
placeholder="укажите ссылку"
onChange={ this.handleInput } />
<button className="submitBtn">Добавить</button>
</form>
</div>
);
}
}
export default AddNavItem;
According to Mozilla docs
The unshift() method adds one or more elements to the beginning of an array and returns the new length of the array.
So when you try to change your state using this, it's returning the length of the array instead of the new array you're trying to update the state to. On top of that, this method is mutable as you are modifying the original array instead of returning a new copy of it. I would suggest changing your code to use the array.prototype.concat() method instead as it keeps your state immutable as it returns a new array instead of modifying the original array.
Try changing your method to this instead.
addNavItem = (item) => {
this.setState({ items: this.state.items.concat(item) });
}
Edit:
If you are using es6, you can use the spread syntax which also returns a new copy of the array.
addNavItem = (item) => {
this.setState({ items: [...this.state.items, item] });
}
Edit 2:
In your Navbar component, you're setting this.props.items to state when it initializes and then you don't update your state when this.props.items change.
You don't need state for this component, I would change your NavBar component to look like this:
import React from 'react';
import NavItem from './NavItem';
const Navbar = (props) => {
return (
<div className="centered navbar grey">
<h2>MyReact</h2>
<ul>
<NavItem items={ props.items }/>
</ul>
</div>
);
}
export default Navbar;
Edit 3:
If you want to keep your items inside state in your NavBar component, then you need to watch for updates being made to the items props on that component.
import React, { Component } from 'react';
import NavItem from './NavItem';
class Navbar extends Component {
state = {
items: this.props.items,
}
componentDidUpdate(prevProps) {
if (this.props.items!== prevProps.items) {
this.setState({ items: this.props.items });
}
}
render() {
return (
<div className="centered navbar grey">
<h2>MyReact</h2>
<ul>
<NavItem items={ this.state.items }/>
</ul>
</div>
);
};
}
export default Navbar;
let arr = [3,4,5]
console.log(arr.unshift(3,5))
addNavItem = (item) => {
let newItems = [...this.state.items];
newItems.unshift(item)
this.setState({
items: newItems
}
});
}
Now you are updating the array and adding the elements to the begining of it, which i assume you wanted and is why you picked upshift. you are then setting the sate equal to the updated array you have.
addNavItem = (item) => {
this.setState((prevState) => {items: [...prevState.items, item]})
}
This should work.

Categories

Resources