NativeBase List not updating - javascript

So I got an object what looks like:
[
{
title
data: [
{
id
name
checked
}
]
},
... ( * n item)
]
These states are in a reducer, so in a store.
When I click on an item, I toggle the checked.
The reducer:
switch(action.type) {
case TOGGLE_ITEM:
const { row, column } = action.payload
let newState = state.slice()
newState[row].data[column].checked = !newState[row].data[column].checked
console.warn(`the references are: ${newState == state ? 'same' : 'different'}`)
return newState
And the component (the Screen is connected to the store):
<List dataArray={list}
renderRow={(item, sectionID, row) =>
<View>
<ListItem itemDivider>
<Text>
{item.title}
</Text>
</ListItem>
<List dataArray={item.data}
renderRow={(subitem, sectionID, column) =>
<Item
toggled={subitem.checked}
text={subitem.name}
onPress={()=>toggleItem(row, column)}
/>
}
/>
</View>
export default connect(
state=>({
list: state.list
}), {
toggleItem
}
)(ListScreen)
When I toggle something, I can see the state is changed in the store, and I can see that the reference is changed too, but yet the List won't trigger the update. :/

You are mutating your state. This is against Redux.
You should return a copy of your state.
const newState = state.map((value, index) => {
if (index != row) {
return value
}
return {
...value,
data: value.data.map((dataValue, dataIndex) => {
if (dataIndex != column) {
return dataValue
}
return {
...dataValue,
checked: !dataValue.checked
}
})
}
})
NOTE: NativeBase will return a "string" index value. That's why we are not using !==. You might cast the row and column to integer via parseInt.

Related

How to sort list of objects within renerItem of flatlist?

Hello I want to sort an object within renderItem of FlatList.I'm getting array of objects as server response.I want to do sorting within these objects.I've a list of cars with their price. If the user click on sort button of car with lowest price first.I want to show the cars with lowest price first in the list.The structure of item within renderItem is as follows.
The expanded structure of single item is as follows.I want to sort the object by its priceTotal.
Following is my code which I've done so far.Please help to find a solution.
sample code
renderItems = ({ item, index }) => {
return (
<CarReservationDetailComponent
carName={item.make}
carType={item.carType}
carPrice1={item.priceTotal}
mileage
mileageText={item.autonomy + 'km. restantes'}
imageUri={item.picture}
/>
)
}
.....
<FlatList
data={this.props.value.value}
renderItem={this.renderItems}
ItemSeparatorComponent={this.renderSeparator}
showsVerticalScrollIndicator={false}
/>
class ParentComponent extends Component {
sortItemByKey = key => {
const { items } = this.state;
const clonedItems = items.map(item => ({ ...item }));
clonedItems.sort(compare);
this.setState({ items: clonedItems });
function compare(a, b) {
if (a[key] < b[key]) return -1;
if (a[key] > b[key]) return 1;
return 0;
}
};
render() {
const { items } = this.State;
return (
<View>
<ChildComponent value={{ value: items }} sortByKey={this.sortItemByKey} />
</View>
);
}
}
class ChildComponent extends Component {
// you should bind this method to onclick of sort button
onClick = () => {
this.props.sortByKey('priceTotal');
};
render() {
return (
<FlatList
data={this.props.value.value}
renderItem={this.renderItems}
ItemSeparatorComponent={this.renderSeparator}
showsVerticalScrollIndicator={false}
/>
);
}
}

onChangeText from TextInput is duplicating the texts on React Native

I am developing a 'Google Forms' style application where you can create many different forms.
I am having a problem in TextInput while answering a form.
First let me explain how the form response system works. When the user opens the screen to answer a form, he / she receives through redux an array of objects containing the form's questions, so I use a map () to scroll through each question and create a new array with the answers, which begin empty. After that I render a TextInput for each text type question. The problem is that when I try to change TextInput's text, it doubles the value.
Example: If I type 'rrk' it will show 'rrkrrk'.
The odd thing is that if I put any console.warn () inside the text change function, the problem goes away and TextInput works normally.
I'm putting two links from two videos that I recorded showing what happens.
With console.warn: https://www.youtube.com/watch?v=6AZE1R46zFA
Without console.warn: https://www.youtube.com/watch?v=2bAId-cP8eY
//Create array with answers based on question type
export const createInputs = (forms, formOpenKey) => {
return dispatch => {
const formData = forms[formOpenKey]
const responses = []
formData.quizes.map((quiz, index) => {
switch(quiz.type) {
case 'TEXT': responses.push(''); break
case 'YES_NO': responses.push(false); break
case 'MULTIPLE_CHOICE': responses.push(0); break
case 'SELECTION': responses.push(Array(quiz.options.length).fill(false)); break
}
})
dispatch({ type: ADD_RESPONSE_SET_INPUTS, payload: responses })
}
}
export const changeResponse = (newValue, indexQuiz, itemIndex, itensArray) => {
return dispatch => {
console.warn('With this warn, TextInput works normally')
let selectionArray
if (itensArray) {
selectionArray = itensArray.slice()
selectionArray[itemIndex] = newValue
}
dispatch({ type: ADD_RESPONSE_CHANGE_ITEM, payload: itensArray ? selectionArray : newValue, id: indexQuiz })
}
}
case ADD_RESPONSE_CHANGE_ITEM:
return { ...state,
formResponse: state.formResponse.map((response, index) => index === action.id
? action.payload
: response
)}
//My class that renders each of the questions to the user
class ResponseInput extends React.Component {
shouldComponentUpdate(nextProps, nextState){
return (JSON.stringify(this.props.formResponse[this.props.index]) !== JSON.stringify(nextProps.formResponse[this.props.index]) || nextProps.formResponseError.includes(this.props.index))
}
render() {
const { quizInfo, index, formResponse, changeResponse, formResponseError, disabled, response } = this.props
console.warn('render input')
return (
<Surface style={ styles.backgroundFormItem }>
<Text style={ styles.title }>{ quizInfo.title }</Text>
{ quizInfo.required ? <Text style={ styles.required }>Resposta Obrigatória</Text> : null }
<View style={{ marginBottom: 10 }}/>
{ getResponseType(quizInfo, index, disabled ? response : formResponse[index], changeResponse, disabled) }
<View style={{ marginBottom: 12 }}/>
{ !disabled && formResponseError.includes(index) ? <View style={ styles.errorLine }/> : null }
</Surface>
)
}
}
mapStateToProps = state => ({
formResponse: state.addResponse.formResponse,
formResponseError: state.addResponse.formResponseError,
})
export default connect(mapStateToProps, { changeResponse })(ResponseInput)
//The function that returns the answer component type based on the question type (I just left the text, which is what matters)
const getResponseType = (info, indexQuiz, response, changeResponse, disabled) => {
switch(info.type) {
case 'TEXT':
return (
<TextInput editable={ !disabled } selectTextOnFocus value={ response } onChangeText={ text => changeResponse(text, indexQuiz) } selectionColor={ colors.primary } style={ styles.responseInputText } placeholder='Escreva a resposta'/>
)
}
}

Redux state single item update showing previous version

My update reducer for my items (icdCode) in my array (icdCodes) is not updating properly within the react component (until I reload the entire component). First it was giving the duplicate key issue because the newly updated item in the array was showing up along with the previous state item within my list component after the action was triggered. I figured a workaround for that with some tweaks, but no matter what else I've tried, I can't update this item properly on the front-end.
Initial state:
state = {icdCodes: []}
The update reducer:
case UPDATE_ICD_CODE:
return {
...state,
icdCodes: [...state.icdCodes, action.payload]
}
Here's an excerpt from my react component loading the list of these array items (via mapping):
render() {
const { icdCodes } = this.props.icdCode;
return (
<Card body>
<ListGroup flush>
<Row>
this.icdCodes.map(({ _id, icdCode, icdCodeValue }) => (
<div>{icdCodeValue}</div>
)
</Row>
</ListGroup>
</Card>
);
}
}
IcdCodeItem.propTypes = {
getIcdCodes: PropTypes.func.isRequired,
icdCode: PropTypes.object.isRequired
};
const mapStateToProps = state => ({
icdCode: state.icdCode
});
export default connect(
mapStateToProps,
{ getIcdCodes, deleteIcdCode, updateIcdCode }
)(IcdCodeItem);
Here is what the action.payload returns (the updated icdCode item with a new value in place of the "icdCode" section):
{icdCodeVersion: "10",
_id: "5b922fbae1c4241b54ea8aa4",
icdCode: "I9",
icdCodeValue: "jam jam",
date: "2018-09-07T07:58:50.104Z", …}
The following code only partly solves the issue (allows me to edit only the first key (not the icdCode item, but the icdCode within the item - apologies for the horrible syntax) of my object rather than the whole object):
return {
...state,
icdCodes: state.icdCodes.map(
icdCode =>
icdCode._id === action.payload._id
? { ...icdCode, icdCode: action.payload.icdCode }
: icdCode
)
};
You can map icdCodes array, then if the element is right (here I'm checking by _id) then you can change it without mutating.
case UPDATE_ICD_CODE: {
const icdCodes = state.icdCodes.map(icd => {
if (icd._id === action.payload._id) {
return { ...icd, icdCode: action.payload.icdCode };
}
return icd;
});
return { ...state, icdCodes };
}
** Update after comments **
If you need to change more than one property here it is:
case UPDATE_ICD_CODE: {
const { _id, icdCode, icdCodeValue } = action.payload;
const icdCodes = state.icdCodes.map(icd => {
if (icd._id === _id) {
return { ...icd, icdCode, icdCodeValue };
}
return icd;
});
return { ...state, icdCodes };
}
If you want to change the object totally, it is easier:
case UPDATE_ICD_CODE: {
const { _id } = action.payload;
const icdCodes = state.icdCodes.map(icd =>
icd._id === _id ? action.payload : icd
)
return { ...state, icdCodes };
}

How to toggle css class of a single element in a .map() function in React

I have a .map() function where I'm iterating over an array and rendering elements, like so:
{options.map((option, i) => (
<TachyonsSimpleSelectOption
options={options[i]}
key={i}
onClick={() => this.isSelected(i)}
selected={this.toggleStyles("item")}
/>
I am toggling the state of a selected element like so:
isSelected (i) {
this.setState({ selected: !this.state.selected }, () => { console.log(this.state.selected) })
}
Using a switch statement to change the styles:
toggleStyles(el) {
switch (el) {
case "item":
return this.state.selected ? "bg-light-gray" : "";
break;
}
}
And then passing it in my toggleStyles method as props to the className of the TachyonsSimpleSelectOption Component.
Problem
The class is being toggled for all items in the array, but I only want to target the currently clicked item.
Link to Sandbox.
What am I doing wrong here?
You're using the selected state incorrectly.
In your code, to determine whether it is selected or not, you depends on that state, but you didn't specify which items that is currently selected.
Instead saving a boolean state, you can store which index is currently selected so that only specified item is affected.
This may be a rough answer, but I hope I can give you some ideas.
on your render:
{options.map((option, i) => (
<TachyonsSimpleSelectOption
options={options[i]}
key={i}
onClick={() => this.setState({ selectedItem: i })}
selected={this.determineItemStyle(i)}
/>
))}
on the function that will determine the selected props value:
determineItemStyle(i) {
const isItemSelected = this.state.selectedItem === i;
return isItemSelected ? "bg-light-gray" : "";
}
Hope this answer will give you some eureka moment
You are not telling react which element is toggled. Since the state has just a boolean value selected, it doesn't know which element is selected.
In order to do that, change your isSelected function to :
isSelected (i) {
this.setState({ selected: i }, () => {
console.log(this.state.selected) })
}
Now, the React state knows that the item on index i is selected. Use that to toggle your class now.
In case you want to store multiple selected items, you need to store an array of indices instead of just one index
TachyonsSimpleSelectOption.js:
import React from 'react';
class Option extends React.Component {
render() {
const { selected, name } = this.props;
return(
<h1
onClick={() => this.props.onClick()}
style={{backgroundColor: selected ? 'grey' : 'white'}}
>Hello {name}!</h1>
)
}
}
export default Option;
index.js:
import React from "react";
import { render } from "react-dom";
import TachyonsSimpleSelectOption from "./TachyonsSimpleSelectOption";
const options = ["apple", "pear", "orange"];
const styles = {
selected: "bg-light-gray"
};
class Select extends React.Component {
constructor(props) {
super(props);
this.state = {
open: false,
selected: []
};
this.handleClick = this.handleClick.bind(this);
this.handleBlur = this.handleBlur.bind(this);
this.isSelected = this.isSelected.bind(this);
}
handleBlur() {
this.toggleMenu(close);
}
handleClick(e) {
this.toggleMenu();
}
toggleMenu(close) {
this.setState(
{
open: !this.state.open
},
() => {
this.toggleStyles("menu");
}
);
}
toggleStyles(el, index) {
switch (el) {
case "menu":
return this.state.open ? "db" : "dn";
break;
case "item":
const { selected } = this.state;
return selected.indexOf(index) !== -1;
break;
}
}
isSelected(i) {
let { selected } = this.state;
if (selected.indexOf(i) === -1) {
selected.push(i);
} else {
selected = selected.filter(index => index !== i);
}
this.setState({ selected});
}
render() {
const { options } = this.props;
return (
<div
className="flex flex-column ba"
onBlur={this.handleBlur}
tabIndex={0}
>
<div className="flex-row pa3" onClick={this.handleClick}>
<span className="flex-grow-1 w-50 dib">Title</span>
<span className="flex-grow-1 w-50 dib tr">^</span>
</div>
<div className={this.toggleStyles("menu")}>
{options.map((option, i) => (
<TachyonsSimpleSelectOption
name={options[i]}
key={i}
onClick={() => this.isSelected(i)}
selected={this.toggleStyles("item", i)}
/>
))}
</div>
</div>
);
}
}
render(<Select options={options} />, document.getElementById("root"));
And Link to Sandbox.

Component can't catch up with redux state

Help me please solve this issue.
I use redux and react-redux to control state in my app.
But when I try to change styles in my Component depending in the value from redux store, it react with delay. When I add new Item and click the list and expect its color being changed, it does this only after I add another item, so that it always delays.
Here is my reducer
export const items = (state = [], action) => {
switch(action.type) {
case 'ADD_ITEM':
const newItem = {
title: action.title,
id: action.id,
selected: action.selected,
comments: action.comments
};
return [
...state,
newItem
];
case 'REMOVE_ITEM':
return state.filter(({ id }) => id !== action.id);
case 'SELECT_ITEM':
state.map((item) => {
if (item.id === action.id) {
return [
...state,
item.selected = true
];
} else {
return [
...state,
item.selected = false
];
}
});
default:
return state;
}
};
Here is my component which I want to react on every change of the redux store
import React from 'react';
import { connect } from 'react-redux';
import { removeItem, selectItem } from '../actions/items';
import { Badge, ListGroup, ListGroupItem, Button } from 'reactstrap';
const stylesForActiveItem = {
borderLeft: '4px solid red',
transition: 'all .5s',
marginLeft: '-4px',
borderRadius: '0'
}
class Item extends React.Component {
constructor(props) {
super(props);
}
render() {
const { removeItem, selectItem, id, title, selected } = this.props;
return (
<ListGroup className="Item">
<ListGroupItem
onClick={() => selectItem({ id })}
className={selected ? 'Item__text active-item' :
'Item__text'}
>{title} <Badge className="Item__badge">14</Badge>
</ListGroupItem>
<Button className="Item__btn" onClick={() => removeItem({ id
})}>Delete</Button>
</ListGroup>
);
}
}
const mapDispatchToProps = (dispatch) => ({
removeItem: (id) => dispatch(removeItem(id)),
selectItem: (id) => dispatch(selectItem(id))
})
export default connect(null, mapDispatchToProps)(Item);
state.map((item) => {
if (item.id === action.id) {
return [
...state,
item.selected = true
];
} else {
return [
...state,
item.selected = false
];
}
});
//I guess you need to do something like this
state.map((item) => {
if (item.id === action.id) {
return {...item, selected:true}
} else {
return {...item, selected:false}
}
});
Since even though map returns new array, internal object should also not get mutated. That is why we spread and create a new item object inside.
There is no need to create arrays again in map with entire state. That will just change your state structure instead of just changing a boolean.

Categories

Resources