I'm following a tutorial on React.js, making a Todo app as an example.
App.js
import React, { Component } from 'react';
import TodoItem from "./components/TodoItem";
import Data from "./Data";
class App extends Component {
constructor(){
super();
this.state = { todos:[Data] }
}
render() {
const TodoItems = this.state.todos.map(item =>
<TodoItem key={item.id} item={item} />)
return (
<div>
{todoItems}
</div>
)
}
}
export default App;
TodoItem.js
import React from 'react';
function TodoItem (props) {
return (
<div>
<input type="checkbox" checked={props.item.completed} />
<p>{props.item.text}</p>
</div>
)
}
export default TodoItem;
Data.js is simply just an array
const Data = [{id: 1, text: "Some random text", completed: true}, //and so on... ]
When I run this, the browser only renders a checkbox, nothing else. Is there something I'm missing? I checked the dev tools by chrome and saw there are props being passed.
The problem is this: this.state = { todos:[Data] }
That doesn't put the contents of Data in todos, like I think you intend to do, it makes todos an array containing Data, which itself is an array, i.e:
todos = [ [ {id: 1, text: "Some random text", completed: true}, ... ] ]
So when you map over this.state.todos, the 'item' you pull out is actually the single item within todos which is in fact the whole Data array! (not the items within Data like you want)
The array has no text property, so no text shows. It also of course has no completed property, but the checkbox does not need that property to exist to get rendered, so you just see one, single, checkbox with no text.
Change it to this, and it should work.
this.state = { todos: Data }
An unrelated thing, sure just a copy/paste typo in the code here, but just for completeness, you have const TodoItems = ... but then reference {todoItems} in the JSX. I guess that should be const todoItems = ....
First and the foremost thing is that, you need to export data from the Data.js file. Secondly, how you have shown your Data variable, i'm assuming it is an array and thus you can set it directly in the state without encapsulating it in squared brackets.
I have also updated the TodoItem to make sure the text is inline with the checkbox.
Check out the code sandbox link below!
Related
so I am new to React. Loving it so far. However, I am having a basic question which doesn't have a clear answer right now.
So, I am learning how to lift the state of a component.
So here's a reproducible example.
index.js
import React from "react";
import ReactDOM from "react-dom"
import {Component} from "react";
// import AppFooter from "./AppFooter";
import AppContent from "./AppContent";
import AppHeader from "./AppHeader";
import 'bootstrap/dist/css/bootstrap.min.css'
import 'bootstrap/dist/js/bootstrap.bundle.min'
import './index.css'
class App extends Component{
constructor(props) {
super(props);
this.handlePostChange = this.handlePostChange.bind(this)
this.state = {
"posts": []
}
}
handlePostChange = (posts) => {
this.setState({
posts: posts
})
}
render() {
const headerProps = {
title: "Hi Keshav. This is REACT.",
subject: "My Subject is Krishna.",
favouriteColor: "blue"
}
return (
<div className="app">
<div>
<AppHeader {...headerProps} posts={this.state.posts} handlePostChange={this.handlePostChange}/>
<AppContent handlePostChange={this.handlePostChange}/>
</div>
</div>
);
}
}
ReactDOM.render(<App/>, document.getElementById("root"))
I am trying to lift the state of posts which is changed in AppContent to AppHeader.
Here's my AppContent.js and AppHeader.js
// AppContent.js
import React, {Component} from "react";
export default class AppContent extends Component{
state = {
posts: []
}
constructor(props) {
super(props); // constructor
this.handlePostChange = this.handlePostChange.bind(this)
}
handlePostChange = (posts) => {
this.props.handlePostChange(posts)
}
fetchList = () => {
fetch("https://jsonplaceholder.typicode.com/posts")
.then((response) =>
response.json()
)
.then(json => {
// let posts = document.getElementById("post-list")
this.setState({
posts: json
})
this.handlePostChange(json)
})
}
clickedAnchor = (id) => {
console.log(`Clicked ${id}`)
}
render() {
return (
<div>
<p>This is the app content.</p>
<button onClick={this.fetchList} className="btn btn-outline-primary">Click</button>
<br/>
<br/>
<hr/>
<ul>
{this.state.posts.map((item) => {
return (
<li id={item.id}>
<a href="#!" onClick={() => this.clickedAnchor(item.id)}>{item.title}</a>
</li>
)
})}
</ul>
<hr/>
<p>There are {this.state.posts.length} entries in the posts.</p>
</div>
)
}
}
// AppHeader.js
import React, {Component, Fragment} from "react";
export default class AppHeader extends Component{
constructor(props) {
super(props); // constructor
this.handlePostChange=this.handlePostChange.bind(this)
}
handlePostChange = (posts) => {
this.props.handlePostChange(posts)
}
render() {
return (
<Fragment>
<div>
<p>There are {this.props.posts.length} posts.</p>
<h1>{this.props.title}</h1>
</div>
</Fragment>
)
}
}
So here's the main question. As we see, that I am calling the dummy posts api and trying to show the titles of the json object list returned by it.
The posts state is actually updated in AppContent and is shared to AppHeader by lifting it to the common ancestor index.js
However, here's what I have observed.
When I keep this code running using npm start I see that anytime I make a change in any place, it refreshes. I was under the impression that it renders the whole page running on localhost:3000.
Say here's my current situation on the web page:
Now, say I make a change in just AppContent.js, then here's how it looks then:
In here, we see that it's still showing 100 posts in case of AppHeader. Is this expected that react only reloads the component and not the whole page. When I refresh the whole page, it shows 0 posts and 0 posts in both the places. Now have I made a mistake in writing the code ? If yes, how do I fix this ?
Thank you.
In case the question is not clear please let me know.
In here, we see that it's still showing 100 posts in case of AppHeader. Is this expected that react only reloads the component and not the whole page.
It's not React, per se, that's doing that. It's whatever you're using to do hot module reloading (probably a bundler of some kind, like Webpack or Vite or Rollup or Parcel or...). This is a very handy feature, but yes, it can cause this kind of confusion.
Now have I made a mistake in writing the code ?
One moderately-signficant one, a relatively minor but important one, and a couple of trivial ones:
posts should either be state in App or AppContent but not both of them. If it's state in both of them, they can get out of sync — as indeed you've seen with the hot module reloading thing. If you want posts to be held in App, fetch it there and provide it to AppContent as a property. (Alternatively you could remove it from App and just have it in AppContent, but then you couldn't show the total number of posts in App.)
When you're rendering the array of posts, you need to have a key on each of the li items so that React can manage the DOM nodes efficiently and correctly.
There's no need to wrap a Fragment around a single element as you are in AppHeader.
If you make handlePostChange an arrow function assigned to a property, there's no reason to bind it in the constructor. (I would make it a method instead, and keep the bind call, but others like to use an arrow function and not bind.)
There's no reason for the wrapper handlePostChange functions that just turn around and call this.props.handlePostChange; just use the function you're given.
Two issues with your fetch call:
You're not checking for HTTP success before calling json. This is a footgun in the fetch API I describe here on my very old anemic blog. Check response.ok before calling response.json.
You're ignoring errors, but should report them (via a .catch handler).
I've been following this tutorial for ReactJS and have been trying now to convert the simplistic Todo App (just checks off and on items) to React Native. I've been using expo to try it live on my phone and everything.
It all went good, but now I'm trying to add something. Whenever I click the checkbox I want to remove the component related to that item.
My idea was:
Since I'm rendering the TodoItem components from an array of todos,
and whenever I click a checkbox it updates the array as a whole
(looking for a certain id and updating it's completed variable). I can
run through the array and whenever the id is different I return the
todo. This way I returned every todo but the one with matching id to
be rendered.
import React, { Component } from 'react';
import { Alert,Image,StyleSheet, Text,Button, View } from 'react-native';
import TodoItem from './TodoItem'
import todosData from "./todosData"
export default class App extends React.Component {
constructor() {
super()
this.state = {
todos: todosData
}
this.handleChange = this.handleChange.bind(this)
}
handleChange(id) {
this.setState(prevState => {
const updatedTodos = this.state.todos.map( todo => {
if(todo.id !== id) {
return todo
}
})
return {
todos:updatedTodos
}
})
}
render() {
const todoItems = this.state.todos.map( item =>
<TodoItem
key={item.id}
item={item}
handleChange = {this.handleChange}
/>
)
return (
<View style={styles.container}>
{todoItems}
</View>
);
}
}
This gives an error: ' TypeError:undefined is not an object (evaluating 'item.id')', giving at App.js:42:18
I'll also add the code referring to the TodoItem:
import React, { Component } from 'react';
import { Alert,Image,StyleSheet, Text,Button, View } from 'react-native';
import { CheckBox } from 'react-native-elements'
function TodoItem(props) {
return (
<View>
<CheckBox
checked={props.item.completed}
onPress={() => props.handleChange(props.item.id)}
/>
<Text>{props.item.text}</Text>
</View>
);
}
export default TodoItem
I don't understand why this won't work. It feels like I'm deleting the component while still using it (for it to give a undefined), but I don't see where. Since I'm simple updating a list of todos.
How can I do the thing I want?
PS: I seem unable to properly format the first segment of code. I apologize for that!
Try this:
handleChange(id) {
const { todos } = this.state
// filter out the deleted one
const filtered = todos.filter(x => x.id !== id)
this.setState({ todos: filtered })
}
We don't want to alter the state directly, but since .filter() creates a new array, without touching the given array, it is fine to use it. if it was another operation, you'd do something like this:
// create a copy
const newSomethings = [...this.state.somethings]
// do whatever with newSomethings
this.setState({ somethings: newSomethings })
I am trying to map over one of the two arrays from my imported js file ProductInformation.js.
This is in my main component class <ProductSquare arrayId = {door}/>
I have also tried <ProductSquare arrayId = {['door']}/>
What I am trying to do is only map the array (out of the two) which matches the panelId prop variable.
I get an error of TypeError: Cannot read property 'map' of undefined
export const door = [
{
id: 1,
productSquareId: 'door',
productImage: require('./door.png'),
companyImage: require('./logo.png'),
price: '$55.99',
},
{
id: 2,
productSquareId: 'door',
productImage: require('./door.png'),
companyImage: require('./logo.png'),
price: '$55.99',
},
]
export const lighting = [
{
id: 1,
productSquareId: 'lighting',
productImage: require('./lighting.png'),
companyImage: require('./logo.png'),
price: '$55.99',
}
import React, { Component } from 'react';
import './ProductSquare.css';
import './grid-container.css'
import {door, lighting} from './ProductInformation.js';
class ProductSquare extends Component {
constructor(props)
{
super(props)
this.state = {
};
}
PopulateProductSquares()
{
const ProductSquares = this.props.arrayId.map((product, i) =>
<div className = "ProductSquare">
<img className = "MainImage" src ={this.props.arrayId[i].productImage} alt = "test"/>
<img className = "CompanyImage" src ={this.props.arrayId[i].companyImage} alt = "test"/>
<button className = "AddButton">
Add
</button>
<button className = "InfoButton">
Info
</button>
</div>
)
return (
ProductSquares
)
}
render() {
return (
this.PopulateProductSquares()
)
}
}
export default ProductSquare;
As Alan pointed out, I think the main problem is that when you are referring to this, it is not bound to the main component. For functions that are not part of the standard React component lifecycle (constructor, render, componentDidMount, etc), you must explicitly state that you bind it to the component like this
constructor(props)
{
super(props)
this.state = {};
this.PopulateProductSquares = this.PopulateProductSquares.bind(this);
}
That by itself should resolve the immediate problem you are facing.
In addition, I would point out a few things that would make your component a little bit easier to read. For example, having the internal function PopulateProductSquares start with a capital letter, makes us think that it is a Class or Component, so I would rename that populateProductSquares (or renderProductSquares in my personal opinion to indicate what it does).
Secondly, when you are looping through the products, you don't need to refer to this.props.arrayId[i] since each object is already passed as the product argument in the function (product, i) => when you use map.
And you don't need to assign the result from this.props.arrayId.map(...) to an constant since you are returning it right away.
Lastly, since the only thing you are doing in the render method is to call the PopulateProductSquares function, it doesn't make sense to separate it into a separate function, you could just write it all directly in render (as Alan also pointed out). But there are a lot of useful cases where you do want to have it in a separate function, so I think it is important to understand the requirement for binding functions.
To summarise, here is how I might have done it (with a slightly different render function to showcase when you might want to have separate functions).
class ProductSquare extends Component {
constructor(props)
{
super(props)
this.state = {};
this.renderProductSquares = this.renderProductSquares.bind(this);
}
renderProductSquares()
{
return this.props.arrayId.map((product, i) =>
<div className = "ProductSquare" key={i}>
<img className = "MainImage" src ={product.productImage} alt = "test"/>
<img className = "CompanyImage" src ={product.companyImage} alt = "test"/>
<button className = "AddButton">
Add
</button>
<button className = "InfoButton">
Info
</button>
</div>
);
}
render() {
return (
<div>
<h1>Here are a bunch of product squares</h1>
{this.renderProductSquares()}
</div>
);
}
}
export default ProductSquare;
I'll take a swing at what you are trying to do here:
<ProductSquare arrayId="door" />
In order to get to the door array of your ProductInformation.js file, it would probably be better to have a default export:
/* ProductInformation.js */
export default {
door: [/* ...door info*/]
window: [/* ...window info */],
};
Then when you import it, you would:
import products from "./ProductInformation.js";
For your map function, you would want to use your products import with your props.arrayId:
const ProductSquares = products[this.props.arrayId].map(...);
In your current code, you are trying to map over the string prop you are passing to your component. What you want is to index the correct product array. You need to either create the default export (written above), or create a map in your render function:
const productMap = { door: door, window: window };
const ProductSquares = productMap[this.props.arrayId].map(...);
I have a react component that receives props from the redux store every second. The new state has an array that's different than the last array. To be specific, every second an element is added to the array. For example:
in one state the array is:
[1, 2, 3, 4, 5, 6]
the next state
[1, 2, 3, 4, 5, 6, 7]
My reducer:
return {
...state,
myList: [ payload, ...state.myList.filter(item => payload.id !== item.id).slice(0, -1) ]
}
Now, in my react component I am subscribing to this state and for every change, the list is re-rendered.
import React, { Component } from 'react';
import MyRow from './MyRow';
class MyList extends Component {
render() {
return (
<div>
{this.props.myList.map((list, index) => (
<MyRow key={list.id} data={list}/>
))}
</div>
);
}
}
function select({ myList }) {
return { myList };
}
export default connect(select)(MyList);
In MyRow.js
import { PureComponent } from 'react';
class MyRow extends PureComponent {
render() {
const data = this.props.data;
return (
<div>
{data.id} - {data.name}
</div>
);
}
}
export default MyRow;
Now, my problem is: It's costly for me to re-render every element that has been already rendered. The MyRow heavily uses styled components and other expensive operations.
This is causing react to re-render the whole list every second when the state is updated. This gets worst if updates come in less than 1 seconds, like 4 updates per second. The react app simply crashes in this case.
Is there any way to only add the newly added item to the list and not re-render the whole list?
Thanks
You're using PureComponent, that do shallow comparison, then your component MyRow should not be rerendered on each new item being added (Please follow my code example below).
Is there any way to only add the newly added item to the list and not re-render the whole list?
According to your question - Yes, using PureComponent should render only 1 time the new item:
Here's what the React's docs says:
If your React component’s render() function renders the same result given the same props and state, you can use React.PureComponent for a performance boost in some cases.
Code example of PureComponent:
You can check out the code sample, that I did for you.
You will see that the Item component is always rendered only 1 time, because we use React.PureComponent. To prove my statement, each time the Item is rendered, I added current time of rendering. From the example you will see that the Item Rendered at: time is always the same, because it's rendered only 1 time.
const itemsReducer = (state = [], action) => {
if (action.type === 'ADD_ITEM') return [ ...state, action.payload]
return state
}
const addItem = item => ({
type: 'ADD_ITEM',
payload: item
})
class Item extends React.PureComponent {
render () {
// As you can see here, the `Item` is always rendered only 1 time,
// because we use `React.PureComponent`.
// You can check that the `Item` `Rendered at:` time is always the same.
// If we do it with `React.Component`,
// then the `Item` will be rerendered on each List update.
return <div>{ this.props.name }, Rendered at: { Date.now() }</div>
}
}
class List extends React.Component {
constructor (props) {
super(props)
this.state = { intervalId: null }
this.addItem = this.addItem.bind(this)
}
componentDidMount () {
// Add new item on each 1 second,
// and keep its `id`, in order to clear the interval later
const intervalId = setInterval(this.addItem, 1000)
this.setState({ intervalId })
}
componentWillUnmount () {
// Use intervalId from the state to clear the interval
clearInterval(this.state.intervalId)
}
addItem () {
const id = Date.now()
this.props.addItem({ id, name: `Item - ${id}` })
}
renderItems () {
return this.props.items.map(item => <Item key={item.id} {...item} />)
}
render () {
return <div>{this.renderItems()}</div>
}
}
const mapDispatchToProps = { addItem }
const mapStateToProps = state => ({ items: state })
const ListContainer = ReactRedux.connect(mapStateToProps, mapDispatchToProps)(List)
const Store = Redux.createStore(itemsReducer)
const Provider = ReactRedux.Provider
ReactDOM.render(
<Provider store={Store}>
<ListContainer />
</Provider>,
document.getElementById('container')
)
<script src="https://unpkg.com/react#16/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom#16/umd/react-dom.development.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/redux/4.0.0/redux.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-redux/5.0.7/react-redux.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/babel-polyfill/6.26.0/polyfill.min.js"></script>
<div id="container">
<!-- This element's contents will be replaced with your component. -->
</div>
Solutions:
If the performance problem is caused by MyRow rerending, please find out what's the reason of rerending, because it should not happen, because of PureComponent usage.
You can try to simplify your reducer, in order to check / debug, is the reducer causing the problem. For instance, just add the new item to the list (without doing anything else as filtrations, slice, etc): myList: [ ...state.myList, payload ]
Please make sure you always pass the same key to your item component <MyRow key={list.id} data={list} />. If the key or data props are changed, then the component will be rerendered.
Here are some other libraries, these stand for efficient rendering of lists. I'm sure they will give us some alternatives or insights:
react-virtualized - React components for efficiently rendering large lists and tabular data
react-infinite - A browser-ready efficient scrolling container based on UITableView
PureComponent will shallowly compare the props and state. So my guess here is that the items are somehow new objects than the previous passed props, thus the rerendering.
I would advice, in general, to only pass primitive values in pure components :
class MyList extends Component {
render() {
return (
<div>
{this.props.myList.map((item, index) => (
<MyRow key={item.id} id={item.id} name={data.name} />
//or it's alternative
<MyRow key={item.id} {...item} />
))}
</div>
);
}
}
//...
class MyRow extends PureComponent {
render() {
const {id, name} = this.props;
return (
<div>
{id} - {name}
</div>
);
}
}
The problem really exists in the reducer.
myList: [ payload, ...state.myList.filter(item => payload.id !== item.id).slice(0, -1) ]
What is the logic implemented using slice(0,-1)?
It is the culprit here.
From your question I understood the next state after [1,2,3] will be [1,2,3,4].
But your code will be giving [4,1,2], then [5,4,1] then [6,5,4].
Now all the elements in the state are new, not in the initial state. See state is not just getting appended it is completely changing.
Please see if you are getting the desired result by avoiding slice.
myList: [ payload, ...state.myList.filter(item => payload.id !== item.id)]
There is quite an easy solution for this. React VDOM is just a diffing algorithm. The only piece missing with your JSX is something called key which is like an id that the diffing algo uses and renders the particular element. Just tag the element with a KEY something like this https://reactjs.org/docs/lists-and-keys.html#keys
<li key={number.toString()}>
{number} </li>
it looks like you are creating a new array each time in the reducer in which all array indices need to be re-calculated. have you tried appending the new node to the end of the list instead of prepending?
Say I have a comp that is inside of a Scene (react-native-router-flux). It lets people choose their favorite fruits.
import React, {Component} from 'react';
import {View, Text, StyleSheet} from 'react-native';
import {MKCheckbox} from 'react-native-material-kit';
var styles = StyleSheet.create({});
export default class PickAFruit extends Component {
render() {
console.log(this.props.fruits);
return (
<View>
{
this.props.fruits.map((x)=> {
return (
<View key={x.key}>
<Text>{x.key}</Text>
<MKCheckbox checked={this.props.checked} key={x.key} onCheckedChange={(e) => {
this.props.update(e, '' + x.key)
}}/>
</View>
)
})
}
</View>
)
}
}
In the parent comp I'm loading the list of fruits from an API in the didMount:
componentDidMount() {
ApiInst.getFruits().then((fruits) => {
console.log(fruits);
console.log(this.props.fruits);
this.props.fruits = fruits;
});
}
I'm also setting a default fruits array in the parent class. It seems like the properties won't load via the API though, the list of fruit is always the "unknown" value, never the new values. Do I need to load the list of fruits before the Profile scene is loaded? When is the correct time to set properties for a component if they will come from an API?
setState seems like the easy answer but these settings don't "feel" like state, they feel like properties that would be injected at build-time (i.e. when the component is built, not the app). Is this a distinction without a real difference?
You can't modify props. Props are passed from parent to child component, and only the parent can change them.
Use setState instead:
this.setState({fruits: fruits});
And access them from state:
<PickAFruit fruits={this.state.fruits} />
You may also want to set a default state in the component constructor:
constructor(props) {
super(this);
this.state = {fruits: null};
}
this.props.fruits = fruits;
won't effect child component, and to be honest - I'm not sure it will work at all. If you don't want to use flux architecture I think the best solution is to update parent's state on componentDidMount() and pass it as props to child component:
componentDidMount() {
ApiInst.getFruits().then((fruits) => {
this.setState({fruits: fruits});
});
}
render() {
return (
<PickAFruit fruits={this.state.fruits} />
);
}
Every state change will invokre render() method, so after API call PickAFruit component will be rerendered, with fruits passed as a props.