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]
Related
I have made for me a Tutorial-Project where I collect various React-Examples from easy to difficult. There is a "switch/case" conditional rendering in App.js, where I - depending on the ListBox ItemIndex - load and execute the selected Component.
I am trying to optimize my React code by removing the "switch/case" function and replacing it with a two dimensional array, where the 1st column contains the Component-Name 2nd column the Object. Further I would like to lazy-load the selected components.
Everything seems to work fine, I can also catch the mouse events and also the re-rendering begins but the screen becomes white... no component rendering.
App.js
import SampleList, { sampleArray } from './SampleList';
class App extends React.Component {
constructor(props) {
super(props);
this.selectedIndex = -1;
}
renderSample(index) {
if((index >= 0) && (index < sampleArray.length)) {
return React.createElement(sampleArray[index][1])
} else {
return <h3>Select a Sample</h3>;
}
}
render() {
return (
<header>
<h1>React Tutorial</h1>
<SampleList myClickEvent={ this.ClickEvent.bind(this) }/>
<p />
<div>
<Suspense> /**** HERE WAS MY ISSUE ****/
{ this.renderSample(this.selectedIndex) }
</Suspense>
</div>
</header>
);
}
ClickEvent(index) {
this.selectedIndex = index;
this.forceUpdate();
}
}
SampleList.js
import React from 'react';
const SimpleComponent = React.lazy(() => import('./lessons/SimpleComponent'));
const IntervalTimerFunction = React.lazy(() => import('./lessons/IntervalTimerFunction'));
const sampleArray = [
["Simple Component", SimpleComponent],
["Interval Timer Function", IntervalTimerFunction]
];
class SampleList extends React.Component {
constructor(props) {
super(props);
this.myRef = React.createRef();
this.selectOptions = sampleArray.map((Sample, Index) =>
<option>{ Sample[0] }</option>
);
}
render() {
return (
<select ref={this.myRef} Size="8" onClick={this.selectEvent.bind(this)}>
{ this.selectOptions }
</select>
);
}
selectEvent() {
this.props.myClickEvent(this.myRef.current.selectedIndex);
}
}
export default SampleList;
export { sampleArray };
You have several issues in that code:
If you use React.lazy to import components dynamically, use Suspense to show a fallback;
The select can listen to the change event, and receive the value of the selected option, that is convenient to pass the index in your case;
Changing a ref with a new index doesn't trigger a re-render of your components tree, you need to perform a setState with the selected index;
I suggest you to switch to hooks, to have some code optimizations;
Code:
import React, { Suspense, useState, useMemo } from 'react';
const SimpleComponent = React.lazy(() => import('./lessons/SimpleComponent'));
const IntervalTimerFunction = React.lazy(() =>
import('./lessons/IntervalTimerFunction'));
const sampleArray = [
['Simple Component', SimpleComponent],
['Interval Timer Function', IntervalTimerFunction],
];
export default function App() {
const [idx, setIdx] = useState(0);
const SelectedSample = useMemo(() => sampleArray[idx][1], [idx]);
const handleSelect = (idx) => setIdx(idx);
return (
<Suspense fallback={() => <>Loading...</>}>
<SampleList handleSelect={handleSelect} />
<SelectedSample />
</Suspense>
);
}
class SampleList extends React.Component {
constructor(props) {
super(props);
}
selectEvent(e) {
this.props.handleSelect(e.target.value);
}
render() {
return (
<select ref={this.myRef} Size="8" onChange={this.selectEvent.bind(this)}>
{sampleArray.map((sample, idx) => (
<option value={idx}>{sample[0]}</option>
))}
</select>
);
}
}
Working example HERE
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>
)
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
});
}
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
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>