So I'm trying to build a Notifications component in React. The component's state holds an array of notifications which are objects. One of their keys is 'seen'. The purpose of seen, as you can probably guess, is mainly visual. Everytime a user clicks on a notification, I run a function that's supposed to set the notification as seen in the database (for consistency) and in the local state (for UI).
The database part works great, but for some reason the state change doesn't work. When I put some console.logs, weirdly enough I see that the 'seen' property changes to 1 before I even call this.setState. I've been at it for hours now and I can't figure out what's happening.
And now, some code:
import React, {Component} from 'react';
import {connect} from 'react-redux';
import classes from './Notifications.module.css';
import * as actions from '../../../store/actions';
import { FontAwesomeIcon } from '#fortawesome/react-fontawesome';
import moment from 'moment';
import {Redirect} from 'react-router-dom';
class Notifications extends Component {
constructor(props) {
super(props);
// Set an interval to update notifications every 4 minutes.
this.update = setInterval(() => {
this.props.fetchNotifications(this.props.username)
}, 240000)
}
state = {
opened: false,
count: 0,
notifications: []
}
componentDidUpdate(prevProps, prevState) {
if (!prevProps.username && this.props.username) this.props.fetchNotifications(this.props.username);
if (!prevProps.notifications && this.props.notifications) {
this.setState({notifications: this.props.notifications, count: this.props.notifications.filter(not => !not.seen).length});
}
if (this.props.notifications) {
if (JSON.stringify(this.state.notifications) !== JSON.stringify(prevState.notifications)) {
this.setState({count: this.state.notifications.filter(not => !not.seen).length})
}
}
}
componentWillUnmount() {
// Clear the update interval
clearInterval(this.update);
}
redirect(model, model_id) {
switch (model) {
case 'sealant_customer':
return <Redirect to={`/profile/sealant-customer/${model_id}`} />;
case 'unapproved_business':
return <Redirect to={`/profile/unapproved-business/${model_id}`} />;
case 'business':
return <Redirect to={`/profile/business/${model_id}`} />;
case 'missed_call':
return <Redirect to={`/data/missed-calls`} />;
default: return null;
}
}
render() {
let content = (
<React.Fragment>
<div className={classes.svgWrapper}>
<p className={classes.counter} style={{opacity: this.state.count === 0 ? '0' : '1'}}>{this.state.count}</p>
<FontAwesomeIcon icon='bell' onClick={() => this.setState(prevState => ({opened: !prevState.opened}))} />
</div>
{this.state.opened && <div className={classes.notificationsWrapper}>
<ul className={classes.notificationsList}>
{this.state.notifications.length !== 0 ? Array.from(this.state.notifications).map(notifi => {
let icon;
switch (notifi.model) {
case 'sealant_customer':
case 'person':
icon = 'user';
break;
case 'business':
case 'unapproved_business':
icon = 'warehouse';
break;
default: icon = 'user';
}
let classArray = [classes.notification];
if (!notifi.seen) classArray.push(classes.unseen);
return (
<li key={notifi.id} className={classArray.join(' ')} onClick={ () => {
// If already seen, simply redirect on click.
if (notifi.seen) return this.redirect(notifi.model, notifi.model_id);
let newNotifications = [...this.state.notifications];
// If not seen, mark as seen in State & in Database.
let index = newNotifications.findIndex(not => notifi.id === not.id);
newNotifications[index].seen = 1;
this.setState({ notifications: newNotifications});
this.props.markSeen(notifi.id, this.props.username);
// Redirect.
return this.redirect(notifi.model, notifi.model_id);
}}>
<div className={classes.iconWrapper}>
<FontAwesomeIcon icon={icon} />
</div>
<div className={classes.textWrapper}>
<p className={classes.msg}>
{notifi.message}
</p>
<label className={classes.ago}>
{moment(notifi.date).fromNow()}
</label>
</div>
</li>
)
}) : <li className={classes.notification} style={{cursor: 'default'}}><p style={{whiteSpace: 'nowrap'}}>No notifications to show...</p></li>}
</ul>
</div>}
</React.Fragment>
)
return (
<div className={classes.wrapper}>
{content}
</div>
)
}
}
const mapStateToProps = state => {
return {
username: state.auth.username,
notifications: state.data.notifications
};
};
const mapDispatchToProps = dispatch => {
return {
fetchNotifications: username => dispatch(actions.fetchNotifications(username)),
markSeen: (id, username) => dispatch(actions.markSeen(id, username))
};
};
export default connect(mapStateToProps, mapDispatchToProps)(Notifications);
Any help would be appreciated.
For future readers - the SOLUTION:
The problem was that when I called the line let newNotifications = [...this.state.notifications]; I really did create a copy of notifications, but a copy which holds the original notification objects inside.
Once I changed the line newNotifications[index].seen = 1 to newNotifications[index] = {...newNotifications[index], seen: 1} everything worked like a charm.
Related
I am new to React and am trying to build an app in which a user can create a card, delete a card, and change the order of the cards array by clicking left or right arrow to switch elements with the element on the left or on the right.
I am struggling to code this functionaliy. I have the function written to switch the card with that on the left, but this function is not doing anything right now. I also do not get any errors in the console from this function, so I really cannot determine where I am going wrong here.
Here is the code so far:
CardList.js will display the form to add a card and display the array of CardItems, passing the functions to switch these items to the left or right ('moveLeft', 'moveRight') as props.
import React from "react";
import CardItem from "./CardItem";
import CardForm from "./CardForm";
import './Card.css';
class CardList extends React.Component {
state = {
cards: JSON.parse(localStorage.getItem(`cards`)) || []
// when the component mounts, read from localStorage and set/initialize the state
};
componentDidUpdate(prevProps, prevState) { // persist state changes to longer term storage when it's updated
localStorage.setItem(
`cards`,
JSON.stringify(this.state.cards)
);
}
render() {
const cards = this.getCards();
const cardNodes = (
<div style={{ display: 'flex' }}>{cards}</div>
);
return (
<div>
<CardForm addCard={this.addCard.bind(this)} />
<div className="container">
<div className="card-collection">
{cardNodes}
</div>
</div>
</div>
);
}
addCard(name) {
const card = {
name
};
this.setState({
cards: this.state.cards.concat([card])
}); // new array references help React stay fast, so concat works better than push here.
}
removeCard(index) {
this.state.cards.splice(index, 1)
this.setState({
cards: this.state.cards.filter(i => i !== index)
})
}
moveLeft(index,card) {
if (index > 1) {
this.state.cards.splice(index, 1);
this.state.cards.splice((index !== 0) ? index - 1 : this.state.cards.length, 0, card)
}
return this.state.cards
}
moveRight(index, card) {
// ?
}
getCards() {
return this.state.cards.map((card) => {
return (
<CardItem
card={card}
index={card.index}
name={card.name}
removeCard={this.removeCard.bind(this)}
moveLeft={this.moveLeft.bind(this)}
moveRight={this.moveRight.bind(this)}
/>
);
});
}
}
export default CardList;
CardItem is taking in those props and ideally handling moving the card left or right in the array once the left or right icon is clicked.
import React from 'react';
import Card from "react-bootstrap/Card";
import Button from "react-bootstrap/Button";
class CardItem extends React.Component {
render() {
return (
<div>
<Card style={{ width: '15rem'}}>
<Card.Header as="h5">{this.props.name}</Card.Header>
<Card.Body>
<Button variant="primary" onClick={this.handleClick.bind(this)}>Remove</Button>
</Card.Body>
<Card.Footer style={{ display: 'flex' }}>
<i class="arrow left icon" onClick={this.leftClick.bind(this)} style={{ color: 'blue'}}></i>
<i class="arrow right icon" onClick={this.rightClick.bind(this)} style={{ color: 'blue'}}></i>
</Card.Footer>
</Card>
</div>
)
}
handleClick(index) {
this.props.removeCard(index)
}
leftClick(index, card) {
this.props.moveLeft(index, card)
}
rightClick(index, card) {
this.props.moveRight(index, card)
}
}
export default CardItem;
Not sure where I am going wrong here. Any help would be appreciated
Edit #1
Hey guys, so I wrote out a different function to handle moving the card to the left, and I decided to bind "this" to that method in the constructor because I was getting errors saying the program could not read it. However, I am still getting errors basically saying that everything is not defined when I pass the function from CardList to CardItem as props. Does anybody know what the problem is? I suspect its my syntax when I call the methods in CardItem.
CardList.js
import React from "react";
import CardItem from "./CardItem";
import CardForm from "./CardForm";
import './Card.css';
class CardList extends React.Component {
constructor(props) {
super();
this.moveLeft = this.moveLeft.bind(this);
this.moveRight = this.moveRight.bind(this);
this.state = {
cards: JSON.parse(localStorage.getItem(`cards`)) || []
// when the component mounts, read from localStorage and set/initialize the state
};
}
componentDidUpdate(prevProps, prevState) { // persist state changes to longer term storage when it's updated
localStorage.setItem(
`cards`,
JSON.stringify(this.state.cards)
);
}
render() {
const cards = this.getCards();
const cardNodes = (
<div style={{ display: 'flex' }}>{cards}</div>
);
return (
<div>
<CardForm addCard={this.addCard.bind(this)} />
<div className="container">
<div className="card-collection">
{cardNodes}
</div>
</div>
</div>
);
}
addCard(name) {
const card = {
name
};
this.setState({
cards: this.state.cards.concat([card])
}); // new array references help React stay fast, so concat works better than push here.
}
removeCard(index) {
this.state.cards.splice(index, 1)
this.setState({
cards: this.state.cards.filter(i => i !== index)
})
}
moveLeft(index, card) {
this.setState((prevState, prevProps) => {
return {cards: prevState.cards.map(( c, i)=> {
// also handle case when index == 0
if (i === index) {
return prevState.cards[index - 1];
} else if (i === index - 1) {
return prevState.cards[index];
}
})};
});
}
moveRight(index, card) {
// ?
}
getCards() {
return this.state.cards.map((card) => {
return (
<CardItem
card={card}
index={card.index}
name={card.name}
removeCard={this.removeCard.bind(this)}
moveLeft={this.moveLeft}
moveRight={this.moveRight}
/>
);
});
}
}
export default CardList;
CardItem.js
import React from 'react';
import Card from "react-bootstrap/Card";
import Button from "react-bootstrap/Button";
class CardItem extends React.Component {
render() {
return (
<div>
<Card style={{ width: '15rem'}}>
<Card.Header as="h5">{this.props.name}</Card.Header>
<Card.Body>
<Button variant="primary" onClick={this.handleClick.bind(this)}>Remove</Button>
</Card.Body>
<Card.Footer style={{ display: 'flex' }}>
<i class="arrow left icon" onClick={leftClick(index, card)} style={{ color: 'blue'}}></i>
<i class="arrow right icon" onClick={rightClick(index, card)} style={{ color: 'blue'}}></i>
</Card.Footer>
</Card>
</div>
)
}
handleClick(index) {
this.props.removeCard(index)
}
leftClick(index, card) {
this.props.moveLeft(index,card)
}
rightClick(index, card) {
this.props.moveRight(index, card)
}
}
export default CardItem;
To update state arrays in React, you shouldn't use splice, push or the [] operator.
Instead use the methods that return a new array object viz. map, filter, concat,slice.
For a detailed explanation, see this article.
So you can do something like :
moveLeft(index,card) {
this.setState((prevState, prevProps)=> {
return {cards: prevState.cards.map((c,i)=> {
// also handle case when index == 0
if(i == index) {
return prevState.cards[index-1];
} else if(i == index-1) {
return prevState.cards[index];
}
})};
});
}
When updating React state using the previous value, always use
setState((prevState,prevProps)=>{ return ...})
as such state updates may be asynchronous. See React docs.
Since you are calling the parent component method from child, it's better to bind these methods in the CardList constructor. Eg:
this.moveLeft = this.moveLeft.bind(this);
this.moveRight ....
I loop through an array of elements:
this.props.connections.map((connection) => (
For each element in this array a card is created. In this card, I implemented a toogle button:
<div id="bookmarkIcon">
{this.state.available ? (
<Tab onClick={this.handleChange} icon={<StarBorderIcon/>}
aria-label="StarBorder"/>) : <Tab onClick={this.handleChange} icon={<StarIcon/>}
aria-label="StarIcon"/>}
</div>
The handle change method changes the value of available to false. The problem is that then I change the state and therefore, ever icon toggles, but I just want to toggle the icon I clicked on. How can I achieve this?
You can create an object which keeps the state as keys.
Here is a working example:
hidden will look something like this {0: true, 1: true, 2: false}
so we can update the corresponding items by their index.
https://codesandbox.io/s/intelligent-black-83cqg?file=/src/App.js:0-577
import React, { useState } from "react";
import "./styles.css";
export default function App() {
const [hidden, setHidden] = useState({});
const list = ["aa", "bb", "cc", "dd"];
const toggleHide = index => {
setHidden({ ...hidden, [index]: !hidden[index] });
};
return (
<div className="App">
{list.map((item, index) => (
<div>
{!!hidden[index] && <span>[HIDDEN]</span>}
{!hidden[index] && <span>[VISIBLE]</span>}
{item} <span onClick={e => toggleHide(index)}>x</span>
</div>
))}
</div>
);
}
Class-Based Component
class PrivacyPolicyDetails extends Component {
constructor(props) {
super(props);
this.state ={
resultData:[],
error:false ,
active: false,
activeIdList:[]
}
this.toggleClass.bind(this);
}
componentDidMount() {
setting.getQuestionAnswerByType('privacy-policy')
.then(res =>{
if(res.data.questionAnswerList.length > 0){
this.setState({
resultData: res.data.questionAnswerList,
})
}else{
this.setState({
error:true
});
}
}
);
}
toggleClass(id) {
const currentState = this.state.active;
this.setState({ active: !currentState});
if(this.state.activeIdList.find(element => element == id)){
this.state.activeIdList = this.state.activeIdList.filter(item => item !== id);
}else{
this.state.activeIdList.push(id);
}
}
render() {
const { product, currency } = this.props;
const {resultData,error,activeIdList} = this.state;
return (
<div>
<h1>Privacy Policy</h1>
{resultData && resultData.length > 0 ? resultData.map(each_policy =>
<div className="item">
<div className="question"
onClick={() => this.toggleClass(each_policy.question_answer_repository_id)}
>
{each_policy.question}
</div>
<p className={(activeIdList.find(element => element == each_policy.question_answer_repository_id))? "active-div" :"hide"}>
<div className="answer">{each_policy.answer}</div>
</p>
</div>
):''}
</div>
);
}
}
const mapStateToProps = (state) => {
return state.setting;
};
export default connect(mapStateToProps)(PrivacyPolicyDetails);
css
.hide{
display: none;
overflow:hidden;
}
.active-div{
display: block;
overflow:hidden;
}
Make the card into its own component and implement the state of the toggle inside of that component. In your parent component just map each card into one of these components. Each card will have its own toggle which uses the state of the card to determine how it should display.
First of all, ive read this question
React-redux connect() cannot wrap component defined as a class extending React.Component
But im still unable to uderstand it since the connect is being done in some kind of upper level, but I dont understand that phase.
This is my current structure:
reduxStore.js
import { createStore } from "redux";
import rootReducer from "../reducers/index";
const store = createStore(rootReducer);
export default store;
action-types.js
export const RENDER_LAYOUT_ELEMENT = "REDER_LAYOUT_ELEMENT"
reduxActions.js
import {RENDER_LAYOUT_ELEMENT} from "../constants/action-types"
export function renderLayoutElement(payload){
return {type: RENDER_LAYOUT_ELEMENT}
};
reduxReducers.js
import {RENDER_LAYOUT_ELEMENT} from "../constants/action-types"
const initialState = {
renderedEl: {
heimdall: false,
skadi: false,
mercator: false
}
}
function renderedElReducer(state = initialState, action){
if(action.type === RENDER_LAYOUT_ELEMENT){
return Object.assign({},state,{
renderedEl: state.renderedEl.concat(action.payload)
})
}
return state
}
export default renderedElReducer;
Now what I want is, to read the renderedEl initialState in a component that I have.
Per my understanding, i need to connect that component, to the store and then with a mapStateToProps it would read the global state (the store)
So i go to my component and try to connect it, but I dont understand where to do it.
The component is big, but basically Im trying to stop using the state of renderedEl, to read it from the store. The state im currnetly using is located in the constructor, it should dissapear because it will now be read from the store.
And in the render, the conditional parts of the style, now wont be set based on the states, but based on the store. I was going to do all that, but problem is, right now im stuck with how to connect this component to the store. All the tutorials I have seen are connected with some sort of functioncal componentes and not class componentes
import React, { Component, useState } from "react";
import brand from "../images/valhallaNaranja.png";
import { FontAwesomeIcon } from '#fortawesome/react-fontawesome'
import { faArrowAltCircleLeft, faArrowAltCircleRight, faUser } from '#fortawesome/free-regular-svg-icons'
import { faColumns } from '#fortawesome/free-solid-svg-icons'
import './css/Sidebar.css'
import { isAdmin } from "../services/userCheck.js"
//select
const mapStateToProps = state => {
return { renderedEl: state.renderedEl }
}
export default class SideBar extends Component {
constructor(props) {
super(props);
this.state = {
retracted: this.props.retracted,
isAdmin: false,
isHovering: false,
//THIS STATE IS WHAT IM TRYING TO REPLACE BY READING FROM THE STORE
renderedEl: {
heimdall: false,
skadi: false,
mercator: false
}
}
this.hoverTrue = this.hoverTrue.bind(this);
this.hoverFalse = this.hoverFalse.bind(this);
}
componentDidMount() {
if (isAdmin()) {
this.setState({
isAdmin: true
})
}
}
componentDidUpdate() {
if (this.state.retracted != this.props.retracted) {
this.setState({
retracted: this.props.retracted
})
}
}
renderEl = (el) => {
var elementName = el.target.getAttribute('id');
var renderedElements = this.state.renderedEl;
for (let key in renderedElements) {
if (key == elementName) {
renderedElements[key] = true
}
}
this.setState({
renderEl: renderedElements
})
}
hoverTrue() {
this.setState({
isHovering: true
})
}
hoverFalse() {
this.setState({
isHovering: false
})
}
render() {
let navbar_brand = this.state.retracted ? "navbar-brand-retracted" : "navbar-brand";
let img_redie = this.state.retracted ? "img-redie-retracted" : "img-redie";
let home_icon = this.state.retracted ? "divicon homeicon-retracted" : "divicon homeicon";
let register_icon = this.state.retracted ? "divicon divicon2 registericon-retracted" : "divicon divicon2 registericon";
let expand_icon = this.state.retracted ? "divicon-no-hover divicon2 expandicon-retracted" : "divicon divicon2 expandicon";
//THOSE STATES WILL BE READ NOW FROM THE STORE
let skadiRendered = this.state.renderedEl.skadi ? "bubbletext bubbletext-rendered" : "bubbletext";
let heimdallRendered = this.state.renderedEl.heimdall ? "bubbletext bubbletext-rendered" : "bubbletext";
let mercatorRendered = this.state.renderedEl.mercator ? "bubbletext bubbletext-rendered" : "bubbletext";
let layoutAppVisualSelector = this.props.history.location.pathname == "/layout" ? "divicon divicon2 expandicon divicon-layout" : "divicon divicon2 expandicon";
return (
<div id="sidebar" className={this.state.retracted ? 'sidebar-retracted' : 'sidebar-expanded'}>
<div /*routerLink=""*/ className={navbar_brand}>
<img alt="Reddie" src={brand} width="60" height="60" className={img_redie} />
</div>
<ul className="nav nav3 navbar-nav">
<li>
{/* Home icon */}
<div className={home_icon} onClick={() => this.props.history.push('/')}>
<svg className="svgicon-sidebar" viewBox="0 0 14 14" >
<path d="M13.9 5.7L7.2.8c-.1-.1-.3-.1-.4 0L.1 5.7c-.1.1-.1.3 0 .5s.3.2.5.1L7 1.6l6.4 4.7c.1 0 .1.1.2.1s.2-.1.3-.1c.1-.3.1-.5 0-.6" />
<path d="M12.1 6.4c-.2 0-.4.2-.4.4v5.8H8.8V9.4c0-1-.8-1.8-1.8-1.8s-1.8.8-1.8 1.8v3.2H2.3V6.7c0-.2-.2-.4-.4-.4s-.4.2-.4.4v6.2c0 .2.2.4.4.4h3.6c.2 0 .3-.1.4-.3V9.4c0-.6.5-1.1 1.1-1.1.6 0 1.1.5 1.1 1.1v3.5c0 .2.2.3.4.3h3.6c.2 0 .4-.2.4-.4V6.7c0-.2-.2-.3-.4-.3" />
</svg>
</div>
</li>
{this.state.isAdmin ? <li>
<div className={register_icon} onClick={() => this.props.history.push('/admin')}>
<FontAwesomeIcon className="registerIcon" icon={faUser} />
</div>
</li> : null}
{(this.props.history.location.pathname != "/layout") && (this.props.history.location.pathname != "/skadi") && (this.props.history.location.pathname != "/heimdall") && (this.props.history.location.pathname != "/mercator") ? null : <li>
<div className={layoutAppVisualSelector} onMouseEnter={this.hoverTrue}
onMouseLeave={this.hoverFalse} >
<FontAwesomeIcon className="registerIcon" icon={faColumns} onClick={() => this.props.history.push({ pathname: '/layout', state: { comeFrom: this.props.history.location.pathname } })} />
{(this.state.isHovering && this.props.history.location.pathname == "/layout") ? <div className="speech-bubble" onMouseLeave={this.hoverFalse}
onMouseEnter={this.hoverTrue}>
<span id="heimdall" className={heimdallRendered} onClick={(el) => this.renderEl(el)}>Heimdall</span> <br /> <span className={skadiRendered} id="skadi" onClick={(el) => this.renderEl(el)}>Skadi</span> <br /> <span id="mercator" className={mercatorRendered} onClick={(el) => this.renderEl(el)}>Mercator</span>
</div> : null}
</div>
</li>}
{(this.state.retracted) || ((this.props.history.location.pathname == "/") || (this.props.history.location.pathname == "/register")) || (this.props.history.location.pathname == "/admin") || (this.props.history.location.pathname == "/layout") ? null : <li>
<div className="divicon divicon2 expandicon " onClick={this.props.retract}>
<FontAwesomeIcon className="registerIcon" icon={faArrowAltCircleLeft} />
</div>
</li>}
</ul>
{this.state.retracted ? <div className="expandicon-retracted-container"> <FontAwesomeIcon className="expandicon-retracted" icon={faArrowAltCircleRight} onClick={this.props.unretract} /> </div> : null}
</div>
)
}
}
How would I go about connecting that component?
First you need to wrap a root component (most recommended index.js or App.js...) with redux Provider
import { Provider } from 'react-redux'
<Provider store={store}>
<App />
</Provider>
After so, store is provided for all parts that under this , and can be connected like that
import { connect } from 'react-redux'
const mapStateToProps = state => {
return { renderedEl: state.renderedEl }
}
class SideBar extends Component {
componentDidMount() {
console.log(this.props.renderedEL)
}
}
export default connect(mapStateToProps)(SideBar)
As for your action, dispatch the action towards the reducer
export function renderLayoutElement(payload){
return dispatch => dispatch({type: RENDER_LAYOUT_ELEMENT})
}
do only export class SideBar [...] for testing purposes, but export default connect(mapStateToProps)(SideBar) which connects your component to redux state and assigns reduced state to props.
Edit: The documentation you're looking for is here.
Probably a simple question and have so many people answered here but in this scenario, I cannot figure out why it doesn't work ...
In the parent I have
updateAccountNumber = value => {
console.log(value);
};
<Child updateAccountNumber={this.updateAccountNumber} />
In the child I have
<ListItem
button
key={relatedSub.office + relatedSub.account}
onClick={() =>
this.props.updateAccountNumber(
relatedSub.office + relatedSub.account
)
}
Even if I do parent like this, still no help..
<Child updateAccountNumber={() => this.updateAccountNumber()} />
if I have the below child item, then when I click on the menu that runs the child items, the component calls itself as many items as there are...
onClick={this.props.updateAccountNumber(
relatedSub.office + relatedSub.account
)}
It won't even run the below code, simple code, I can't see why it wouldn't kick of the handleClick event...
import React, { Component } from "react";
import List from "#material-ui/core/List";
import ListItem from "#material-ui/core/ListItem";
import ListItemText from "#material-ui/core/ListItemText";
const handleClick = () => {
debugger;
alert("sda");
console.log("bbb");
};
export default class RelatedSubAccounts extends Component {
Links = () => {
if (this.props.RelatedSubAccounts) {
let RelatedSubArray = this.props.RelatedSubAccounts;
let source = RelatedSubArray.map(relatedSub => (
<ListItem
button
onClick={handleClick}
key={relatedSub.office + relatedSub.account}
className={
relatedSub.office + relatedSub.account !== this.props.OfficeAccount
? ""
: "CurrentRelatedSub"
}
>
<ListItemText primary={relatedSub.office + relatedSub.account} />
</ListItem>
));
return (
<div id="RelatedSubLinks">
<List>{source}</List>
</div>
);
} else {
return "";
}
};
render() {
return this.Links();
}
}
Please ask if any other related code is missing, and I can share.
I was able to get an example that works with the code you shared by using RelatedSubAccounts like this:
<RelatedSubAccounts RelatedSubAccounts={[{ office: 1, account: 2 }]} />
Code Sandbox Example
I see a few things that stand out as potentially confusing. I will point them out in code below with comments:
import React, { Component } from "react";
import List from "#material-ui/core/List";
import ListItem from "#material-ui/core/ListItem";
import ListItemText from "#material-ui/core/ListItemText";
const handleClick = () => {
debugger;
alert("RelatedSubAccounts clicked");
console.log("bbb");
};
export default class RelatedSubAccounts extends Component {
// Capitalization like this in react normally indicates a component
Links = () => {
/*
Having a prop that is the same name as the component and capitalized is confusing
In general, props aren't capitalized like the component unless you are passing a component as a prop
*/
if (this.props.RelatedSubAccounts) {
// Again, capitalization on RelatedSubArray hints that this is a component when it really isn't
let RelatedSubArray = this.props.RelatedSubAccounts;
let source = RelatedSubArray.map(relatedSub => (
<ListItem
button
onClick={handleClick}
key={relatedSub.office + relatedSub.account}
className={
relatedSub.office + relatedSub.account !== this.props.OfficeAccount
? ""
: "CurrentRelatedSub"
}
>
<ListItemText primary={relatedSub.office + relatedSub.account} />
</ListItem>
));
return (
<div id="RelatedSubLinks">
<List>{source}</List>
</div>
);
} else {
return "";
}
};
render() {
return this.Links();
}
}
This is going to be strangest solution but might give you a lesson or two.. I found the cause of the issue here.
So I have a menu like this
When you click on that arrow to open up the menu, it becomes active and when you click away onBlur kicks in and that menu goes away.. (menu created used react-select Creatable)
DropdownIndicator = props => {
return (
<div
onBlur={() => {
this.setState({ Focused: false, RelatedSub: false });
}}
so I had to update it to below:
onBlur={() => {
this.setState({ Focused: false });
setTimeout(() => {
this.setState({ RelatedSub: false });
}, 100);
}}
I am trying to filter my array object list and then trying to display in the ListView with new DataSource. However, the list is not getting filtered. I know that my filter function works correctly. ( I checked it in the console.log )
I am using Redux to map my state to prop. And then trying to filter the prop. Is this the wrong way?
Here is my code:
/*global fetch:false*/
import _ from 'lodash';
import React, { Component } from 'react';
import { ListView, Text as NText } from 'react-native';
import { connect } from 'react-redux';
import { Actions } from 'react-native-router-flux';
import {
Container, Header, Item,
Icon, Input, ListItem, Text,
Left, Right, Body, Button
} from 'native-base';
import Spinner from '../common/Spinner';
import HealthImage from '../misc/HealthImage';
import { assetsFetch } from '../../actions';
const ds = new ListView.DataSource({
rowHasChanged: (r1, r2) => r1 !== r2
});
class AssetsList extends Component {
componentWillMount() {
this.props.assetsFetch();
// Implementing the datasource for the list View
this.createDataSource(this.props.assets);
}
componentWillReceiveProps(nextProps) {
// Next props is the next set of props that this component will be rendered with.
// this.props is still equal to the old set of props.
this.createDataSource(nextProps.assets);
}
onSearchChange(text) {
const filteredAssets = this.props.assets.filter(
(asset) => {
return asset.name.indexOf(text) !== -1;
}
);
this.dataSource = ds.cloneWithRows(_.values(filteredAssets));
}
createDataSource(assets) {
this.dataSource = ds.cloneWithRows(assets);
}
renderRow(asset) {
return (
<ListItem thumbnail>
<Left>
<HealthImage health={asset.health} />
</Left>
<Body>
<Text>{asset.name}</Text>
<NText style={styles.nText}>
Location: {asset.location} |
Serial: {asset.serial_number}
</NText>
<NText>
Total Samples: {asset.total_samples}
</NText>
</Body>
<Right>
<Button transparent onPress={() => Actions.assetShow()}>
<Text>View</Text>
</Button>
</Right>
</ListItem>
);
}
render() {
return (
<Input
placeholder="Search"
onChangeText={this.onSearchChange.bind(this)}
/>
<ListView
enableEmptySections
dataSource={this.dataSource}
renderRow={this.renderRow}
/>
);
}
}
const mapStateToProps = state => {
return {
assets: _.values(state.assets.asset),
spinner: state.assets.asset_spinner
};
};
export default connect(mapStateToProps, { assetsFetch })(AssetsList);
What am I doing wrong here?
It's a little hard to follow what's going on here. I would simplify it to be like so:
class AssetsList extends Component {
state = {};
componentDidMount() {
return this.props.assetsFetch();
}
onSearchChange(text) {
this.setState({
searchTerm: text
});
}
renderRow(asset) {
return (
<ListItem thumbnail>
<Left>
<HealthImage health={asset.health} />
</Left>
<Body>
<Text>{asset.name}</Text>
<NText style={styles.nText}>
Location: {asset.location} |
Serial: {asset.serial_number}
</NText>
<NText>
Total Samples: {asset.total_samples}
</NText>
</Body>
<Right>
<Button transparent onPress={() => Actions.assetShow()}>
<Text>View</Text>
</Button>
</Right>
</ListItem>
);
}
getFilteredAssets() {
}
render() {
const filteredAssets = this.state.searchTerm
? this.props.assets.filter(asset => {
return asset.name.indexOf(this.state.searchTerm) > -1;
})
: this.props.assets;
const dataSource = ds.cloneWithRows(filteredAssets);
return (
<Input
placeholder="Search"
onChangeText={this.onSearchChange.bind(this)}
/>
<ListView
enableEmptySections
dataSource={dataSource}
renderRow={this.renderRow}
/>
);
}
}
const mapStateToProps = state => {
return {
assets: _.values(state.assets.asset),
spinner: state.assets.asset_spinner
};
};
export default connect(mapStateToProps, { assetsFetch })(AssetsList);
A few points:
Your component is stateful. There is one piece of state that belongs only to the component: the search term. Keep that in component state.
Don't change the data source in life cycle functions. Do it the latest point you know it's needed: in render.
I'm guessing that there's something async in assetFetch, so you probably should return it in componentDidMount.
I changed from componentWillMount to componentDidMount. It's recommended to put async fetching componentDidMount. This can matter if you ever do server side rendering.
Skip filtering if there is no search term. This would only matter if the list is very large.
One thing I have a little concern with is the pattern of fetching inside a component, putting it in global state, and then relying on that component to react to the global state change. Thus changing global state becomes a side effect of simply viewing something. I assume you are doing it because assets is used elsewhere, and this is a convenient point to freshen them from the server so that they will show up in other components that do not fetch them. This pattern can result in hard-to-find bugs.
You need to do setState to trigger render. Here's how I would do it -
constructor(props) {
super(props);
this.ds = new ListView.DataSource({ rowHasChanged: (r1,r2) => r1 !== r2 });
this.state = {
assets: []
};
}
componentWillMount() {
this.props.assetsFetch();
this.setState({
assets: this.props.assets
});
}
componentWillReceiveProps(nextProps) {
this.setState({
assets: nextProps.assets
});
}
onSearchChange(text) {
const filteredAssets = this.props.assets.filter(asset => asset.name.indexOf(text) !== -1);
this.setState({
assets: _.values(filteredAssets)
});
}
render() {
...
<ListView
dataSource={this.ds.cloneWithRows(this.state.assets)}
.....
/>
}