Pass state value to component - reactjs

I am really new in React.js. I wanna pass a state (that i set from api data before) to a component so value of selectable list can dynamically fill from my api data. Here is my code for fetching data :
getListSiswa(){
fetch('http://localhost/assessment-app/adminpg/api/v1/Siswa/')
.then(posts => {
return posts.json();
}).then(data => {
let item = data.posts.map((itm) => {
return(
<div key={itm.siswa_id}>
<ListItem
value={itm.siswa_id}
primaryText={itm.nama}
/>
</div>
)
});
this.setState({item: item});
});
}
From that code, i set a state called item. And i want to pass this state to a component. Here is my code :
const ListSiswa = () => (
<SelectableList>
<Subheader>Daftar Siswa</Subheader>
{this.state.item}
</SelectableList>
);
But i get an error that say
TypeError: Cannot read property 'item' of undefined
I am sorry for my bad explanation. But if you get my point, i am really looking forward for your solution.
Here is my full code for additional info :
import React, {Component} from 'react';
import PropTypes from 'prop-types';
import {List, ListItem, makeSelectable} from 'material-ui/List';
import Subheader from 'material-ui/Subheader';
let SelectableList = makeSelectable(List);
function wrapState(ComposedComponent) {
return class SelectableList extends Component {
static propTypes = {
children: PropTypes.node.isRequired,
};
getListSiswa(){
fetch('http://localhost/assessment-app/adminpg/api/v1/Siswa/')
.then(posts => {
return posts.json();
}).then(data => {
let item = data.posts.map((itm) => {
return(
<div key={itm.siswa_id}>
<ListItem
value={itm.siswa_id}
primaryText={itm.nama}
/>
</div>
)
});
this.setState({item: item});
});
}
componentWillMount() {
this.setState({
selectedIndex: this.props.defaultValue,
});
this.getListSiswa();
}
handleRequestChange = (event, index) => {
this.setState({
selectedIndex: index,
});
};
render() {
console.log(this.state.item);
return (
<ComposedComponent
value={this.state.selectedIndex}
onChange={this.handleRequestChange}
>
{this.props.children}
</ComposedComponent>
);
}
};
}
SelectableList = wrapState(SelectableList);
const ListSiswa = () => (
<SelectableList>
<Subheader>Daftar Siswa</Subheader>
{this.state.item}
</SelectableList>
);
export default ListSiswa;

One way to do it is by having the state defined in the parent component instead and pass it down to the child via props:
let SelectableList = makeSelectable(List);
function wrapState(ComposedComponent) {
return class SelectableList extends Component {
static propTypes = {
children: PropTypes.node.isRequired,
};
componentWillMount() {
this.setState({
selectedIndex: this.props.defaultValue,
});
this.props.fetchItem();
}
handleRequestChange = (event, index) => {
this.setState({
selectedIndex: index,
});
};
render() {
console.log(this.state.item);
return (
<ComposedComponent
value={this.state.selectedIndex}
onChange={this.handleRequestChange}
>
{this.props.children}
{this.props.item}
</ComposedComponent>
);
}
};
}
SelectableList = wrapState(SelectableList);
class ListSiswa extends Component {
state = {
item: {}
}
getListSiswa(){
fetch('http://localhost/assessment-app/adminpg/api/v1/Siswa/')
.then(posts => {
return posts.json();
}).then(data => {
let item = data.posts.map((itm) => {
return(
<div key={itm.siswa_id}>
<ListItem
value={itm.siswa_id}
primaryText={itm.nama}
/>
</div>
)
});
this.setState({item: item});
});
}
render() {
return (
<SelectableList item={this.state.item} fetchItem={this.getListSiswa}>
<Subheader>Daftar Siswa</Subheader>
</SelectableList>
);
}
}
export default ListSiswa;
Notice that in wrapState now I'm accessing the state using this.props.item and this.props.fetchItem. This practice is also known as prop drilling in React and it will be an issue once your app scales and multiple nested components. For scaling up you might want to consider using Redux or the Context API. Hope that helps!

The error is in this component.
const ListSiswa = () => (
<SelectableList>
<Subheader>Daftar Siswa</Subheader>
{this.state.item}
</SelectableList>
);
This component is referred as Stateless Functional Components (Read)
It is simply a pure function which receives some data and returns the jsx.
you do not have the access this here.

Related

TypeError: Cannot read property 'map' of undefined When trying to map multiple arrays

I am trying to map multiple arrays at the same time and im not sure if this is how you do it. I am getting the error
TypeError: Cannot read property 'map' of undefined
When trying the following code
import React, { Component } from 'react';
import Axios from 'axios';
import NavBar from '../header-footer/nav-bar'
import Featured from './FeaturedMealplan'
import RecipeItem from './RecipeItem'
export default class MealPlanDetail extends Component {
constructor(props) {
super(props);
this.state = {
currentId: this.props.match.params.slug,
mealplanItem: {}, // Full mealplan
mealplanRecipes: [], // Contains recipe names and difficulty.
}
}
getMealplanItem() {
Axios.get(`http://localhost:5000/get-mealplan/${this.state.currentId}`
).then(response => {
console.log("response", response)
this.setState({
mealplanItem: response.data.mealplan,
mealplanRecipes: this.state.mealplanRecipes.concat(response.data.mealplan["recipes"]),
mealplanIngredients: this.state.mealplanIngredients.concat(response.data.mealplan["recipe_info"]),
recipeItem: response.data.mealplan.recipes
})
}).catch(error => {
console.log("mealplan-detail GET Error ", error)
})
}
componentDidMount() {
this.getMealplanItem();
}
render() {
const renderRecipe = this.state.recipes.map((recipe, idx) => {
return (
<div key={idx}>
<h1>{recipe.recipe_name}</h1>
<h2>Recipe Difficulty: <span>{recipe.recipe_dificulty}</span></h2>
<div>
<RecipeItem recipeItem={this.state.recipeItem} />
</div>
</div>
)
})
return (
<div>
<NavBar/>
<Featured/>
{renderRecipe}
</div>
)
}
}
Data that is given: https://pastebin.com/uYUuRY6U
I just need to be able to format it correctly which this is how I would like it formatted in the renderRecipe return. I am new to mapping and do not know if there is a way to fix or a better way.
Some issues in the code that we can improve on:
this.state.recipes seems to be undefined in your logic. Is it a typo?
I would suggest implementing renderRecipe as a function instead of a variable.
You would only hope to render renderRecipe when there is data, but when your component is being mounted, this.state.recipes is undefined. It would only have value when getMealplanItem gets a response and being defined in the callback. So you should check whether the value is defined before rendering.
Please refer to my comments in the code below.
import React, { Component } from "react";
import Axios from "axios";
import NavBar from "../header-footer/nav-bar";
import Featured from "./FeaturedMealplan";
import RecipeItem from "./RecipeItem";
export default class MealPlanDetail extends Component {
constructor(props) {
super(props);
this.state = {
// ... define `recipes` if that's what you want
};
}
getMealplanItem() {
Axios.get(`http://localhost:5000/get-mealplan/${this.state.currentId}`)
.then((response) => {
console.log("response", response);
// ... set state `recipes` here if that's what you want
this.setState({
mealplanItem: response.data.mealplan,
mealplanRecipes: this.state.mealplanRecipes.concat(
response.data.mealplan["recipes"]
),
mealplanIngredients: this.state.mealplanIngredients.concat(
response.data.mealplan["recipe_info"]
),
recipeItem: response.data.mealplan.recipes
});
})
.catch((error) => {
console.log("mealplan-detail GET Error ", error);
});
}
componentDidMount() {
this.getMealplanItem();
}
render() {
const renderRecipe = () => {
// change renderRecipe from a variable to a function
if (!this.state?.recipes) {
// check whether `recipes` is a defined value
return null;
}
return this.state.recipes.map((recipe, idx) => {
return (
<div key={idx}>
<h1>{recipe.recipe_name}</h1>
<h2>
Recipe Difficulty: <span>{recipe.recipe_dificulty}</span>
</h2>
<div>
<RecipeItem recipeItem={this.state.recipeItem} />
</div>
</div>
);
});
};
return (
<div>
<NavBar />
<Featured />
{renderRecipe()} // it's a function call now
</div>
);
}
}
There is never a this.state.recipes defined. Based on data type and comment
this.state = {
currentId: this.props.match.params.slug,
mealplanItem: {}, // Full mealplan
mealplanRecipes: [], // Contains recipe names and difficulty.
}
I will assume you meant for it to really be this.state.mealplanRecipes.
Your render then becomes
const renderRecipe = this.state.mealplanRecipes.map((recipe, idx) => {...
This can easily handle the initial render with an empty array.

Understanding react js constructor in children component

I would like to understand the behavior of react component constructor. Let suppose I have three components - PageComponent, ListComponent, ItemComponent. My pseudo-code structure is:
PageComponent (get data from redux, fetch data)
ListComponent (obtains data as props, in loop (map) renders list of ItemComponents)
ItemComponent (obtains item data as props, renders item, manipulate data)
Logic:
- when data in ItemComponent changes, changes are stored in REDUX and this change caused list re-rendering.
Use-case 1:
- PageComponent renders ListComponent and ListComponent renders list of ItemComponets
- when REDUX listItem data chages, PageComponent is updated, ListComponent is updated and ItemComponent CONSTRUCTOR is called (its local state is reset)
Use-case 2:
- PageComponent renders only LIST (using map loop) of ItemComponents.
- when REDUX listItem data chages, PageComponent is updated ItemComponent CONSTRUCTOR is NOT called (component is "only" updated) (and its local state is NOT reset)
Why there is a different behavior in these examples?
Source code:
PageComponent:
import React from 'react'
...
class UsersPage extends React.Component {
constructor(props) {
super(props)
props.actions.getUsers();
}
render() {
const {users} = this.props
return (
<Main>
{/* // NO ITEM CONSTRUCTOR IS CALLED
users.data.items.map((item, index) => {
return <ListItemComponent
data={item}
itemMethods={{
getItem: (data) => this.props.actions.getUser(data),
onEdit: (data) => this.props.actions.updateUser(data),
onDelete: (data) => this.props.actions.deleteUser(data),
validation: (data) => validateInput(this.props.strings, data)
}}
key={index}
/>
})*/
}
{ // ITEM CONSTRUCTOR IS CALLED
<ListComponent
loading={users.isFetching}
data={users.data}
methods={{
getItem: (data) => this.props.actions.getUser(data),
onEdit: (data) => this.props.actions.updateUser(data),
onDelete: (data) => this.props.actions.deleteUser(data),
validation: (data) => validateInput(this.props.strings, data)
}}
/>}
</Main>
);
}
}
UsersPage.propTypes = {
users: PropTypes.object.isRequired,
strings: PropTypes.object.isRequired,
}
function mapStateToProps(state) {
return {
users: state.users,
strings: state.strings.data || {},
};
}
function mapDispatchToProps(dispatch) {
return {
actions: bindActionCreators({
getUsers,
getUser,
addUser,
updateUser,
deleteUser,
}, dispatch)
};
}
export default connect(
mapStateToProps,
mapDispatchToProps
)(withAlert(UsersPage));
ListComponent:
import React from 'react'
...
class ListComponent extends React.Component {
getList() {
return <div className="list-outer">
<Row>
{
items.map((item, index) => {
return <ListItemComponent
data={item}
itemMethods={methods}
key={index}
/>
})
}
</Row>
</div>
}
render() {
const {loading} = this.props
return (
<div className="list-wrapper">
{
loading ? <Spinner visible={true}/>
:
this.getList()
}
</div>
)
}
}
ListComponent.propTypes = {
loading: PropTypes.bool.isRequired,
data: PropTypes.object.isRequired,
methods: PropTypes.object.isRequired,
}
export default ListComponent
ListItemComponent:
import React from 'react'
...
class ListItemComponent extends React.Component {
constructor(props) {
super(props)
this.state = {
editMode: false,
}
}
toggleEditMode(){
const editMode = this.state.editMode
this.setState({editMode: !editMode})
}
onEdit(id) {
const itemMethods = this.props.itemMethods
this.toggleEditMode()
itemMethods.getItem({id: id})
}
onDelete(item) {
//...
}
getFields(rowData, index) {
return <div key={index}>
{
rowData.map((itm, idx) => {
return <div key={idx}>{itm.label}: {itm.value}</div>
})
}
</div>
}
render() {
const editMode = this.state.editMode
const {data, itemMethods, strings} = this.props
return (
editMode ?
<Form
id={data.id}
onSubmit={(data) => itemMethods.onEdit(data)}
validation={(data) => itemMethods.validation(data)}
onCloseForm={() => this.toggleEditMode()}
/>
:
<Col xs={12}>
<div>
<div
{this.getFields(data)}
</div>
<div className="controls">
<button
className="btn btn-theme inverse danger"
onClick={() => this.onDelete(data)}
>{strings.delete}</button>
<button
onClick={() => this.onEdit(data.id)}
className="btn btn-theme" type="button"
>
{strings.edit}
</button>
</div>
</div>
</Col>
)
}
}
ListItemComponent .propTypes = {
strings: PropTypes.object.isRequired,
data: PropTypes.object.isRequired,
itemMethods: PropTypes.object.isRequired,
}
function mapStateToProps(state) {
return {
strings: state.strings.data || {}
};
}
export default connect(
mapStateToProps,
null,
)(ListItemComponent )
Ensure each ItemComponent has a key prop set. When React renders your list of items, it needs to know how to identify each element and React leaves it up to you to do this. If you omit the key prop, React will destroy and re-create your list upon each re-render, which means calling the component constructor.
If you provide the exact code you're using, we can better point out where your issue is coming from.
You can read more about lists and keys here.
SOLVED
It was cause by ListComponent and the loading prop that was placed as condion in render function. When item was edited, prop loading was set to true, spinner became visible AND it was the only element in ListComponent and therefore the list items were unmounted

How do I manage my array of children components' states?

I'm new to react, so forgive me. I'm having a problem understanding states, specifically those of children.
Purpose: I'm trying to create a form that a user can append more and more components -- in this case, images.
What happens: User appends 2 or more images. User tries to upload an image with UploadButton component, but both the images are the same. I believe this has to do with both appended children sharing the same state.
Question: How do I give each appended child its own image without affecting the other appended children?
class Page extends Component
constructor (props) {
super(props);
this.state = {
id: '',
numChildren: 0,
images: [],
}
this.onAddChild = this.onAddChild.bind(this);
}
showModal() {
this.setState({
numChildren: 0,
images: [],
});
}
renderModal()
const children = [];
//Here's my array of child components
for(var i = 0; i < this.state.numChildren; i += 1) {
children.push(<this.ChildComponent key={i} />);
}
return (
<ReactModal>
<this.ParentComponent addChild={this.onAddChild}>
{children}
</this.ParentComponent>
</ReactModal>
)
}
onAddChild = () => {
this.setState({
numChildren: this.state.numChildren + 1
})
}
ParentComponent = (props) => (
<div>
{props.children}
<Button onClick={props.addChild}>Add Item</Button>
</div>
);
ChildComponent = () => (
<div>
<UploadButton
storage="menus"
value={this.state.images}
onUploadComplete={uri => this.setState({images: uri})}
/>
</div>
);
}
Here's the code for UploadButton:
import React, { Component } from 'react';
import uuid from 'uuid';
import firebase from '../config/firebase';
class UploadButton extends Component {
constructor(props) {
super(props);
this.state = {
isUploading: false
}
}
handleClick() {
const input = document.createElement("INPUT");
input.setAttribute("type", "file");
input.setAttribute("accept", "image/gif, image/jpeg, image/png");
input.addEventListener("change", ({target: {files: [file]}}) => this.uploadFile(file));
input.click();
}
uploadFile(file) {
console.log('F', file);
const id = uuid.v4();
this.setState({ isUploading: true })
const metadata = {
contentType: file.type
};
firebase.storage()
.ref('friends')
.child(id)
.put(file, metadata)
.then(({ downloadURL }) => {
this.setState({ isUploading: false })
console.log('Uploaded', downloadURL);
this.props.onUploadComplete(downloadURL);
})
.catch(e => this.setState({ isUploading: false }));
}
render() {
const {
props: {
value,
style = {},
className = "image-upload-button",
},
state: {
isUploading
}
} = this;
return (
<div
onClick={() => this.handleClick()}
className={className}
style={{
...style,
backgroundImage: `url("${this.props.value}")`,
}}>
{isUploading ? "UPLOADING..." : !value ? 'No image' : ''}
</div>
);
}
}
export default UploadButton;
I tried to exclude all unnecessary code not pertaining to my problem, but please, let me know if I need to show more.
EDIT: This is my attempt, it doesn't work:
//altered my children array to include a new prop
renderModal() {
const children = [];
for (var i = 0; i < this.state.numChildren; i += 1) {
children.push(<this.ChildComponent imageSelect={this.onImageSelect} key={i} />);
}
//...
};
//my attempt to assign value and pass selected image back to images array
ChildComponent = () => (
<div>
<UploadButton
storage="menus"
value={uri => this.props.onImageSelect(uri)} //my greenness is really apparent here
onUploadComplete={uri => this.setState({images: uri})}
/>
//...
</div>
);
//added this function to the class
onImageSelect(uri) {
var el = this.state.images.concat(uri);
this.setState({
images: el
})
}
I know I'm not accessing the child prop correctly. This is the most complexity I've dealt with so far. Thanks for your time.
When you write this.state in Child / Parent component, you are actually accessing the state of Page. Now, I would recommend that you pass in the index of the child to the Child like so
children.push(<this.ChildComponent key={i} index={i}/>)
so that each children deals with only its own image like so
ChildComponent = ({index}) => (
<div>
<UploadButton
storage="menus"
value={this.state.images[index]}
onUploadComplete={uri => {
let images = this.state.images.slice()
images[index] = uri
this.setState({images})
}}
/>
</div>
);

How to fix recursively updating state?

I am bulding an app using newsapi. i am facing two issue on my state. i fetch data using api and assign it to my state. and use it in my view.
Issue no 1
My view gets rendered before my app receives the data.
Issue no 2
When I try to update my state after a new fetch. it recursively updates the set of data again and again.
import React, {Component} from 'react';
import NewsComponent from './NewsComponent/NewsComponent'
class News extends Component {
state = {
displayStatus: false,
newsItems: []
};
toogleDisplayHandler = () => {
if(this.state.displayStatus===true){
this.setState({displayStatus:false})
}
else{
this.setState({displayStatus:true})
}
}
render(){
const NewsAPI = require('newsapi');
const newsapi = new NewsAPI('d6da863f882e4a1a89c5152bd3692fb6');
//console.log(this.props.keyword);
newsapi.v2.topHeadlines({
sources: 'bbc-news,abc-news',
q: this.props.keyword
}).then(response => {
//console.log(response)
response.articles.map(article => {
//console.log(article);
return(
//console.log(this.state.newsItems)
this.setState({
newsItems: [...this.state.newsItems, article],
})
//this.state.newsItems.push(article)
)
});
});
let Article = null;
Article = (
<div>
{
this.state.newsItems.map((news, index) => {
return (
<NewsComponent key={index}
title={news.title}
url={news.url}
description={news.description}
author={news.author}
publish={news.publishedAt}
image={news.urlToImage}
/>
)
})
}
</div>
)
return (
<div className="App">
{Article}
<button onClick={this.toogleDisplayHandler}>
{this.state.displayStatus === true ? "Hide Article" : "Display Articles"}
</button>
</div>
)
}
}
export default News;
Please help me to resolve this issue.
You should never setState in render as that would cause an infinite loop. Do it in componentDidMount or the constructor.
I would also recommend not using map for simply iterating over a list. Array.map is a function that is useful for returning an array that is constructed by iterating over another array. If you want to run some code for each element of an array use Array.forEach instead.
Like this:
import React, { Component } from "react";
import NewsComponent from "./NewsComponent/NewsComponent";
class News extends Component {
state = {
displayStatus: false,
newsItems: []
};
toogleDisplayHandler = () => {
if (this.state.displayStatus === true) {
this.setState({ displayStatus: false });
} else {
this.setState({ displayStatus: true });
}
};
componentDidMount = () => {
const NewsAPI = require("newsapi");
const newsapi = new NewsAPI("d6da863f882e4a1a89c5152bd3692fb6");
newsapi.v2
.topHeadlines({
sources: "bbc-news,abc-news",
q: this.props.keyword
})
.then(response => {
response.articles.forEach(article => {
this.setState({
newsItems: [...this.state.newsItems, article]
});
});
});
};
render() {
let Article = null;
Article = (
<div>
{this.state.newsItems.map((news, index) => {
return (
<NewsComponent
key={index}
title={news.title}
url={news.url}
description={news.description}
author={news.author}
publish={news.publishedAt}
image={news.urlToImage}
/>
);
})}
</div>
);
return (
<div className="App">
{Article}
<button onClick={this.toogleDisplayHandler}>
{this.state.displayStatus === true
? "Hide Article"
: "Display Articles"}
</button>
</div>
);
}
}
export default News;
1) You can add a check either your state has the data which you want to show on screen to render the view.
2) Please use ComponentDidMount React life cycle function to fetch data from an external source and update this data in the state. In the Render method, it will keep calling it recursively.

TODO project is not woking properly

Components ->
Box
Todolist
Add
AddModal
Main component App
But it is not working that is when I add a new task. It does not get added properly.
I think I cannot use this.setstate twice in a function.
Hope I am correct
Here is given the main component.
App.js :
import React, { Component } from 'react';
import './App.css';
import Box from './Components/Box';
import Add from './Components/Add';
import Todolist from './Components/Todolist';
class App extends Component {
constructor(props) {
super(props);
this.state = {
lists: '',
inputValue: '',
itemArray: []
}
}
onAddTask = () => {
this.setState ({
lists: this.state.inputValue
});
const item = this.state.itemArray;
const title = this.state.lists;
item.push({ title })
this.setState(prevState => ({
itemArray: [...prevState.lists, title]
}))
}
updateInputValue = (event) => {
this.setState({
inputValue: event.target.value
});
}
render() {
let length = this.state.itemArray.length;
return (
<div className="App">
<Box createTodo = {
<div>
{this.state.itemArray.map((itemArr) => {
return (
<div className="box">
<Todolist tasks = {itemArr} />
</div>
)
})}
</div>
}>
</Box>
<Add addTask = {this.onAddTask} inputValues = {this.updateInputValue} inputV = {this.state.inputValue} />
</div>
);
}
}
export default App;
Your addTasks function is not correct, you are mixing up things here.
In your inputValue you save the current value from the input field right? So if you write the following
this.setState({
lists: this.state.inputValue
});
you set your todo list to this single value. And your todo list is not an array anymore.
Secondly, state is imutable. So if you write the following
this.state.itemArray.push({ title });
the state will not be updated. What you actually want is the following:
onAddTask = () => {
this.setState({
itemArray: [...this.state.itemArray, this.state.inputValue]
})
}
And I'm not sure what the lists property on the state is for. You don't use it anywhere besides in your onAddTask function. So I guess you can remove it.

Resources