Map of undefined after deleting an item - React - javascript

I am quite new to React and in this circumstance I feel like I should be using a lifecycle hook, but I'm not sure which. In the code below I am making a call to the API and retrieving the list of users object which contains another list of things they own called members. Like so:
When clicking the button 'Unlink' I am then storing the reference in state and posting that back up to the API to unlink the member:
import React, { Fragment } from 'react';
import FormScene from 'nwiadmin/scenes/form';
import Button from 'nwiadmin/components/button';
import { formatDate } from 'nwiadmin/utility/formatters';
import Modal from 'nwiadmin/components/modal';
import ModalActions from 'nwiadmin/components/modal/modalactions';
import SingleBusinesses from 'app/components/businesses';
let businessRef = '';
class UserBusinessSingleScene extends FormScene {
/* eslint-disable class-methods-use-this */
setInitialState(props) {
const pendingData = {
...props.data,
};
const reference = businessRef;
const isVisible = false;
this.setReferenceTarget = this.setReferenceTarget.bind(this);
return {
pendingData,
reference,
isVisible,
};
}
shouldComponentUpdate(nextProps, nextState) {
console.log('should cmp update', nextProps, nextState);
return true;
}
UNSAFE_componentWillUpdate(nextProps, nextState) {
console.log('cmp will update', nextProps, nextState);
}
componentDidUpdate(prevProps, prevState) {
console.log('cmp did update', prevProps, prevState);
}
setLabel(data) {
return data.name;
}
setMethod() {
return 'post';
}
setRemote() {
return `businesses/unclaim?reference=${this.state.reference}`;
}
modalToggleHander() {
this.setState(prevState => ({
isVisible: !prevState.isVisible,
}));
}
setReferenceTarget() {
this.setState({ reference: businessRef });
}
render() {
console.log(this.state.reference);
return (
<Fragment>
{this.state.pendingData.members.map(business => (
<SingleBusinesses
key={business.reference}
name={business.name}
reference={business.reference}
claimed={`Claimed on: ${formatDate(business.created_at)}`}
unlink={
<Button
onClick={() => {
businessRef = business.reference;
this.setReferenceTarget();
this.modalToggleHander();
}}
>
Unlink User
</Button>
}
/>
))}
<Modal isOpen={this.state.isVisible} title="Are you sure you want to unlink the user?">
<ModalActions
submit={() => this.submit()}
submitText="Unlink User"
cancel={() => this.modalToggleHander()}
/>
</Modal>
</Fragment>
);
}
}
export default UserBusinessSingleScene;
Once unlinked I get a Uncaught TypeError: Cannot read property 'map' of undefined error. I'm not 100% sure why but I feel like it's something to do with the state and that I should be setting the state after unlinking? Any help is appreciated.

Related

Uncaught TypeError: _this.props.data.map is not a function, Map error to render Component

I don't know what is happening with the component im trying to map an array of objects from mongo here but it is no showing nothing to printInventory, but if i call to this.props.inventory and it takes all the data! What is happening?
printInventory = () => {
this.props.inventory.map((inventory) => {
return (
<CardInventario
cardTitle={inventory.name}
quantity={inventory.quantity}
description={inventory.price}
/>
)
})
}
in these function.
Next I going to show the actions and the reducers:
inventoryReducer:
import {TAKE_INVENTORY} from '../../types/inventoryTypes';
const INITIAL_STATE ={
inventory: []
}
function invetoryReducer(state = INITIAL_STATE,action){
switch (action.type) {
case TAKE_INVENTORY:
return {...state, inventory: action.payload}
break;
default:
return state;
break;
}
}
export default invetoryReducer;
and here is the inventoryActions:
import axios from 'axios'
import { TAKE_INVENTORY } from '../../types/inventoryTypes'
export const takeInventory = () => async (dispatch) =>{
const res = await axios.get('http://localhost:3001/inventory')
dispatch({
type: TAKE_INVENTORY,
payload: res.data
})
}
And the full component:
import React, { Component } from 'react'
import { Link } from 'react-router-dom'
import { connect } from 'react-redux'
import axios from 'axios'
/* Components and Styles */
import CardInventario from '../components/CardInventario'
import '../assets/styles/container/Stock.scss'
/* Redux */
import * as inventoryActions from '../actions/inventoryActions'
class Stock extends Component {
componentDidMount() {
this.props.takeInventory();
}
printInventory = () => {
this.props.inventory.map((inventory) => {
return (
<CardInventario
cardTitle={inventory.name}
quantity={inventory.quantity}
description={inventory.price}
/>
)
})
}
render() {
console.log(this.props.inventory)
return (
<>
<div className="container">
<div className="additem">
<Link to="/additem" className="additem__link">
<p className="link-add">
AƱadir Item a Stock
</p>
</Link>
</div>
<div className="stock" key="stock">
{this.printInventory()}
</div>
</div>
</>
)
}
}
const mapStateToProps = (reducers) => {
return reducers.inventoryReducer;
}
export default connect(mapStateToProps, inventoryActions)(Stock);
First of all, please use the correct mapping.
inventoryReducer is not the one you're looking to map.
inventory inside that object is the one you want.
const mapStateToProps = (reducers) => {
return reducers.inventoryReducer.inventory;
}
Also if you get data in this.props.inventory, it should be related to duplicated keys
Please try the following
printInventory = () => {
this.props.inventory.map((inventory, index) => {
return (
<CardInventario
key={index}
cardTitle={inventory.name}
quantity={inventory.quantity}
description={inventory.price}
/>
)
})
}
If you don't have id, it is possible to use index instead (not recommended though)
printInventory = () => {
this.props.inventory.map((inventory) => {
return (
<CardInventario
key={inventory.id}
cardTitle={inventory.name}
quantity={inventory.quantity}
description={inventory.price}
/>
)
})
}

ReactNative) Questions about calling a function that references data within the render()

The program flows as follows:
Home.js => Add.js => Detail.js => Repeat...
Home.js lists the posts.
Add.js is a page for entering the data required for a post. Move through stack navigation from Home.js
Detail.js will take over the data entered in Add.js as a parameter, show you the information of the post to be completed, and press the Done button to move to Home.js.
Home.js contains posts that have been completed.
If a post is created, eatObject is assigned an object from Detail.js.
Const { eatObject } = this.props.route?params || {};
In Home.js, state has an array of objects toEats.
Add eatObject to the object list
{Object.values(toEats).map(toEat =toEat.id toEat.idToEat key={toEat.id} {...toEat} deleteToEat={this._deleteToEat} />)}
Currently, the refresh button updates the 'Home.js' list whenever a post is added. But I want the list of posts to be updated automatically whenever 'Home.js' is uploaded.
I tried to call '_pushToEat' in 'componentDidMount', but 'eatObject' exists in 'render' so it seems impossible to hand it over to parameters.
I want to automatically call '_pushTooEat' when the components of 'Home.js' are uploaded.
Home.js
import React from "react";
import { View, Text, Button } from "react-native";
import ToEat from "./ToEat"
export default class Home extends React.Component {
state = {
toEats:{}
}
render() {
const { toEats } = this.state;
const { eatObject } = this.props.route?.params || {};
// error
// if (eatObject) {
// () => this._pushToEat(eatObject)
// }
return (
<View>
{Object.values(toEats).map(toEat => <ToEat key={toEat.id} {...toEat} deleteToEat={this._deleteToEat} />)}
<Button title="refresh" onPress={()=>this._pushToEat(eatObject)}/>
<View>
<Button title="Add" onPress={() => this.props.navigation.navigate("Add")} />
</View>
</View>
);
}
_deleteToEat = () => {
}
_pushToEat = (eatObject) => {
console.log("function call!")
console.log(eatObject)
this.setState(prevState => {
const newToEatObject = eatObject
const newState = {
...prevState,
toEats: {
...prevState.toEats,
...newToEatObject
}
}
return { ...newState };
})
}
}
Detail.js
import React from "react";
import { View, Text, Button } from "react-native";
import uuid from 'react-uuid'
import { connect } from 'react-redux';
import { inputId } from '../src/reducers/infoReducer';
class Detail extends React.Component {
render() {
const { name, dosage, note } = this.props.route.params;
return (
<View>
<Text>name: {name}</Text>
<Text>dosage: {dosage}</Text>
<Text>note: {note}</Text>
<Button
title="Done"
onPress={() =>
this.props.navigation.navigate(
"Home", {
eatObject: this._createToEat(uuid(), name, dosage)
})
}
/>
</View>
)
}
_createToEat = (id, name, dosage) => {
const ID = id
inputId(id)
const newToEatObject = {
[ID]: {
id: ID,
name: name,
dosage: dosage,
}
}
return newToEatObject;
}
}
function mapStateToProps(state) {
return {
state: state.infoReducer
};
}
function matchDispatchToProps(dispatch) {
return {
inputId: (id) => {
dispatch(inputId(id));
},
}
}
export default connect(mapStateToProps, matchDispatchToProps)(Detail);
I want your advice.
Thank you

React Compound Components type warning

I have a simple compound component with a bunch of static subcomponents:
// #flow
import React, { Component, Children } from 'react';
type Props = {
children: React.ChildrenArray<React.Node> | React.Node,
}
class Toggle extends Component<Props> {
static On = props => (props.on ? props.children : null);
static Off = props => (props.on ? null : props.children);
static Button = props => (
<button
onClick={props.toggle}
type="button"
style={{ display: 'inline-block' }}
>
<pre>{JSON.stringify(props.on, null, 2)}</pre>
</button>
);
state = { on: false }
toggle = () => {
this.setState(
({ on }) => ({ on: !on }),
// maybe this.props.someCallback
() => console.log(this.state.on),
);
}
render() {
return Children.map(
this.props.children,
childElem => React.cloneElement(childElem, {
on: this.state.on,
toggle: this.toggle,
}),
);
}
}
export default Toggle;
The warning happens when I try to put some other elements into Toggle children scope.
For example:
<Toggle>
<Toggle.On>On</Toggle.On>
<span /> <-- this is savage
<Toggle.Button />
<Toggle.Off>Off</Toggle.Off>
</Toggle>
Everything is working, but my flowtype warn me about this span like so:
Warning: Received `false` for a non-boolean attribute `on`.....
Warning: Invalid value for prop `toggle` on <span> tag....
How can I to pacify this nasty girl?
Thank you guys, I think, right solution is just check if type of mounted node is correct one, otherwise - just clone node with regular node props:
// #flow
import React, { Component, Children } from 'react';
type Props = {
children: React.ChildrenArray<React.Node> | React.Node,
}
class Toggle extends Component<Props> {
static On = props => (props.on ? props.children : null);
static Off = props => (props.on ? null : props.children);
static Button = props => (
<button
onClick={props.toggle}
type="button"
style={{ display: 'inline-block' }}
>
<pre>{JSON.stringify(props.on, null, 2)}</pre>
</button>
);
state = { on: false }
toggle = () => {
this.setState(
({ on }) => ({ on: !on }),
// maybe this.props.someCallback
() => console.log(this.state.on),
);
}
// Checking here
allowedTypes = ({ type }) => {
return [
(<Toggle.On />).type,
(<Toggle.Off />).type,
(<Toggle.Button />).type,
].includes(type);
}
render() {
return Children.map(
this.props.children,
(childElem) => {
const elemProps = this.allowedTypes(childElem) ? {
on: this.state.on,
toggle: this.toggle,
} : childElem.props;
return React.cloneElement(childElem, elemProps);
},
);
}
}
export default Toggle;
You can also do this, just having the components in a list and checking their type inside .map, putting on the custom props or otherwise just returning the original child.
const allowedTypes = [ToggleOn, ToggleOff, ToggleButton]
return React.Children.map(props.children, child => {
if (allowedTypes.includes(child.type)) {
return React.cloneElement(child, {on, toggle})
}
return child
})
}

Unmount component on click in child component button // React

I am struggling with successfully removing component on clicking in button. I found similar topics on the internet however, most of them describe how to do it if everything is rendered in the same component. In my case I fire the function to delete in the child component and pass this information to parent so the state can be changed. However I have no idea how to lift up the index of particular component and this is causing a problem - I believe.
There is a code
PARENT COMPONENT
export class BroadcastForm extends React.Component {
constructor (props) {
super(props)
this.state = {
numberOfComponents: [],
textMessage: ''
}
this.UnmountComponent = this.UnmountComponent.bind(this)
this.MountComponent = this.MountComponent.bind(this)
this.handleTextChange = this.handleTextChange.bind(this)
}
MountComponent () {
const numberOfComponents = this.state.numberOfComponents
this.setState({
numberOfComponents: numberOfComponents.concat(
<BroadcastTextMessageForm key={numberOfComponents.length} selectedFanpage={this.props.selectedFanpage}
components={this.state.numberOfComponents}
onTextChange={this.handleTextChange} dismissComponent={this.UnmountComponent} />)
})
}
UnmountComponent (index) {
this.setState({
numberOfComponents: this.state.numberOfComponents.filter(function (e, i) {
return i !== index
})
})
}
handleTextChange (textMessage) {
this.setState({textMessage})
}
render () {
console.log(this.state)
let components = this.state.numberOfComponents
for (let i = 0; i < components; i++) {
components.push(<BroadcastTextMessageForm key={i} />)
}
return (
<div>
<BroadcastPreferencesForm selectedFanpage={this.props.selectedFanpage}
addComponent={this.MountComponent}
textMessage={this.state.textMessage} />
{this.state.numberOfComponents.map(function (component) {
return component
})}
</div>
)
}
}
export default withRouter(createContainer(props => ({
...props
}), BroadcastForm))
CHILD COMPONENT
import React from 'react'
import { createContainer } from 'react-meteor-data'
import { withRouter } from 'react-router'
import { BroadcastFormSceleton } from './BroadcastForm'
import './BroadcastTextMessageForm.scss'
export class BroadcastTextMessageForm extends React.Component {
constructor (props) {
super(props)
this.handleChange = this.handleChange.bind(this)
this.unmountComponent = this.unmountComponent.bind(this)
}
handleChange (e) {
this.props.onTextChange(e.target.value)
}
unmountComponent (id) {
this.props.dismissComponent(id)
}
render () {
console.log(this.props, this.state)
const textMessage = this.props.textMessage
return (
<BroadcastFormSceleton>
<div className='textarea-container p-3'>
<textarea id='broadcast-message' className='form-control' value={textMessage}
onChange={this.handleChange} />
</div>
<div className='float-right'>
<button type='button'
onClick={this.unmountComponent}
className='btn btn-danger btn-outline-danger button-danger btn-small mr-3 mt-3'>
DELETE
</button>
</div>
</BroadcastFormSceleton>
)
}
}
export default withRouter(createContainer(props => ({
...props
}), BroadcastTextMessageForm))
I am having problem with access correct component and delete it by changing state. Any thoughts how to achieve it?
Please fix the following issues in your code.
Do not mutate the state of the component. Use setState to immutably change the state.
Do not use array index as the key for your component. Try to use an id field which is unique for the component. This will also help with identifying the component that you would need to unmount.
Try something like this. As mentioned before, you don't want to use array index as the key.
class ParentComponent extends React.Component {
constructor() {
this.state = {
// keep your data in state, as a plain object
textMessages: [
{
message: 'hello',
id: '2342334',
},
{
message: 'goodbye!',
id: '1254534',
},
]
};
this.handleDeleteMessage = this.handleDeleteMessage.bind(this);
}
handleDeleteMessage(messageId) {
// filter by Id, not index
this.setState({
textMessages: this.state.textMessages.filter(message => message.id !== messageId)
})
}
render() {
return (
<div>
{this.state.textMessages.map(message => (
// Use id for key. If your data doesn't come with unique ids, generate them.
<ChildComponent
key={message.id}
message={message}
handleDeleteMessage={this.handleDeleteMessage}
/>
))}
</div>
)
}
}
function ChildComponent({message, handleDeleteMessage}) {
function handleClick() {
handleDeleteMessage(message.id)
}
return (
<div>
{message.message}
<button
onClick={handleClick}
>
Delete
</button>
</div>
);
}

React function getting called on every change

I am building an isomorphic React app. The workflow I am currently working through is :
User navigates to the /questions route which makes the API call server side and loads the data on the page. This calls the renderData() function like it should and loads all the questions for the user to see.
User clicks add button to add new question and a modal pops up for a user to enter in the form fields and create a new question.
With every change in the modal, the renderData() function is getting called (which it shouldn't). When the user clicks the Create Question button, the renderData() function is also getting called is throwing an error because the state changes.
I can't pinpoint why the renderData() function is getting called every single time anything happens in the modal. Any ideas as to why this is happening and how to avoid it?
Main component :
import React, { Component, PropTypes } from 'react';
import withStyles from 'isomorphic-style-loader/lib/withStyles';
import s from './QuestionsPage.scss';
import QuestionStore from '../../stores/QuestionStore';
import QuestionActions from '../../actions/QuestionActions';
import Loader from 'react-loader';
import QuestionItem from '../../components/QuestionsPage/QuestionItem';
import FloatButton from '../../components/UI/FloatButton';
import AddQuestionModal from '../../components/QuestionsPage/AddQuestionModal';
const title = 'Questions';
class QuestionsPage extends Component {
constructor(props) {
super(props);
this.state = QuestionStore.getState();
this.onChange = this.onChange.bind(this);
this.openModal = this.openModal.bind(this);
this.closeMOdal = this.closeModal.bind(this);
}
static contextTypes = {
onSetTitle: PropTypes.func.isRequired,
};
componentWillMount() {
this.context.onSetTitle(title);
QuestionStore.listen(this.onChange);
}
componentWillUnmount() {
QuestionStore.unlisten(this.onChange);
}
onChange(state) {
this.setState(state);
}
openModal = () => {
this.setState({ modalIsOpen: true});
}
closeModal = () => {
this.setState({ modalIsOpen: false});
}
createQuestion = () => {
const date = new Date();
const q = this.state.question;
q.createdAt = date;
this.setState({ question : q });
QuestionStore.createQuestion(this.state.question);
}
textChange = (val) => {
const q = this.state.question;
q.text = val;
this.setState({ question : q });
}
answerChange = (val) => {
const q = this.state.question;
q.answer = val;
this.setState({ question : q });
}
tagChange = (val) => {
const q = this.state.question;
q.tag = val;
this.setState({ question : q });
}
companyChange = (val) => {
const q = this.state.question;
q.company = val;
this.setState({ question : q });
}
renderData() {
return this.state.data.map((data) => {
return (
<QuestionItem key={data.id} data={data} />
)
})
}
render() {
return (
<div className={s.root}>
<div className={s.container}>
<h1>{title}</h1>
<div>
<Loader loaded={this.state.loaded} />
<FloatButton openModal={this.openModal}/>
<AddQuestionModal
open = {this.state.modalIsOpen}
close = {this.closeModal}
createQuestion = {this.createQuestion}
changeText = {this.textChange}
changeAnswer = {this.answerChange}
changeTag = {this.tagChange}
changeCompany = {this.companyChange}
/>
{ this.renderData() }
</div>
</div>
</div>
);
}
}
export default withStyles(QuestionsPage, s);
Modal Component :
import React, { Component, PropTypes } from 'react';
import QuestionStore from '../../stores/QuestionStore';
import QuestionActions from '../../actions/QuestionActions';
import Modal from 'react-modal';
import TextInput from '../UI/TextInput';
import Button from '../UI/Button';
class AddQuestionModal extends Component {
createQuestion = () => {
this.props.createQuestion();
}
closeModal = () => {
this.props.close();
}
changeText = (val) => {
this.props.changeText(val);
}
changeAnswer = (val) => {
this.props.changeAnswer(val);
}
changeTag = (val) => {
this.props.changeTag(val);
}
changeCompany = (val) => {
this.props.changeCompany(val);
}
render() {
return (
<Modal
isOpen={this.props.open}
onRequestClose={this.closeModal} >
<TextInput
hintText="Question"
change={this.changeText} />
<TextInput
hintText="Answer"
change={this.changeAnswer} />
<TextInput
hintText="Tag"
change={this.changeTag} />
<TextInput
hintText="Company"
change={this.changeCompany} />
<Button label="Create Question" onSubmit={this.createQuestion} disabled={false}/>
<Button label="Cancel" onSubmit={this.closeModal} disabled={false}/>
</Modal>
);
}
}
export default AddQuestionModal;
On click of
It's happening because every change causes you to call the setState method and change the state of the main component. React will call the render function for a component every time it detects its state changing.
The onChange event on your inputs are bound to methods on your main component
Each method calls setState
This triggers a call to render
This triggers a call to renderData
React allows you to change this by overriding a shouldComponentUpdate function. By default, this function always returns true, which will cause the render method to be called. You can change it so that only certain changes to the state trigger a redirect by comparing the new state with the old state.

Categories