Forgive me, I'm new to normalizr+redux. I've managed to normalize my data and create a reducer and end up with :
state = {
installations:{
"1":{...},
"2":{...}
}
}
I would then like to filter this data for use in a UI component into two separate categories (in this case where the installation.operator is equal to the current user). I've managed an implementation that works however it seems exhaustive:
const mapStateToProps = (state, ownProps) => {
console.log("mapStateToProps", state.installations);
let assignedInstallations = Object.keys(state.installations)
.filter(i => {
return state.installations[i].operator == state.login;
})
.map(i => {
return state.installations[i];
});
let unassignedInstallations = Object.keys(state.installations)
.filter(i => {
return state.installations[i].operator != state.login;
})
.map(i => {
return state.installations[i];
});
return {
assignedInstallations,
unassignedInstallations,
loginUserId: state.login
};
};
I'm also new to ES6 and am not across all the new syntax shortcuts etc so I suspect there are much better ways to do this.
Is there a more succinct approach with a similar outcome?
you can do this with only one reduce():
const mapStateToProps = (state, ownProps) => {
console.log("mapStateToProps", state.installations);
let {assignedInstallations,
unassignedInstallations } = Object.keys(state.installations)
.reduce(function(acc, cur, i){
if(state.installations[i].operator == state.login){
acc.assignedInstallations.push(state.installations[i]);
}else{
acc.unassignedInstallations .push(state.installations[i]);
}
return acc
}, {assignedInstallations: [], unassignedInstallations: [] })
return {
assignedInstallations,
unassignedInstallations,
loginUserId: state.login
};
};
lodash (An utility library) have a notion of collection (Here is an example https://lodash.com/docs/4.17.4#filter for filter function). It takes as input Object or Array and returns an Array. It seems to fit to your needs. Here is the refactored code:
import {
filter,
} from 'lodash'
const mapStateToProps = (state, ownProps) => {
let assignedInstallations = filter(state.installations, installation => installation.operator == state.login);
let unassignedInstallations = filter(state.installations, installation => installation.operator != state.login);
return {
assignedInstallations,
unassignedInstallations,
loginUserId: state.login
};
};
Related
This might be a silly question, but how do I get data if I saved the state using prevState.
I am trying to retrieve data from database and send that data through navigation.
const [dataFromDatabase, setDataFromDatabase] = useState('');
const retrieveFromDatabase = () => {
db.transaction(
tx => {
tx.executeSql("SELECT * FROM PreLoveeedTable",
[],
(_, { rows }) => {
console.log("ROWS RETRIEVED");
// clear data currently stored
setDataFromDatabase('');
let entries = rows._array;
entries.forEach((entry) => {
setDataFromDatabase(prev => prev + `${entry.id}, ${entry.image}, ${entry.title}, ${entry.price}, ${entry.description}\n
});
},
(_, result) => {
console.log('SELECT failed!');
console.log(result);
}
)
}
);
}
{dataFromDatabase} will give me the whole entire data in the database.
But wanted to have each entry in the Database. For example entry for title.
I have been stuck on this for a while now and would be appreciated it if i can get a hint.
I'm trying to write a getter for my state the problem is that it returns an undefined value but I'm 100% sure that in articleEan is an object that has an Are_EanNr value of 1234567.
This is the getter I'm writing is supposed to return the first object in the articleEan Array that has the same EanNr as the parameter.
const getters = {
findArByEan: state => {
return (eancode) => { // logging eancode results in 1234567
state.articleEan.find(item => {
return item.Are_EanNr === eancode
})
}
}
}
Where's my mistake?
After Changing it to:
findArByEan: (state) => (eancode) => state.articleEan.find(item => item.Are_EanNr === eancode),
Problem still occurs. This is how I'm calling the getter from a component:
const getters = {
...useGetters('vm', [
'orderRow',
'customer',
'article',
'additionalData',
'findArByEan',
]),
...useGetters('user', ['user']),
};
const Ar = getters.findArByEan.value(eancode.value); // Ar = undefined
Edit:
When looping over the state I'm getting just the indices of the object in array.
log('ArtEan:', artEan.value); // correct output => Array with 38 objects
for(const item in artEan.value) {
log(item); // Logs just the index of array
}
Your second arrow function does not return anything so it's undefined
const getters = {
findArByEan: state => {
return (eancode) => { // logging eancode results in 1234567
return state.articleEan.find(item => {
return item.Are_EanNr === eancode
})
}
}
}
You can also do it this way without any return.
const getters = {
findArByEan: (state) => (eancode) => state.articleEan.find(item => item.Are_EanNr === eancode)
}
I would recommand reading the arrow function documentation.
For example, those 2 functions works the same but one is tidier ;)
const numberSquare1 = (number) => {
return number * number
}
const numberSquare2 = (number) => number * number
console.log(numberSquare1(2))
console.log(numberSquare2(2))
I am using fetch to get some data from an API, I convert this to JSON and want to sort it into different categories. For example tickets (which is what I'm retrieving) with the status active should be in a different array than the ones with status waiting on customer. I want to use a for loop to sort through the results. Should I do this in the same function they're fetched in?
Did a bit of googling but couldn't find a post on this.
methods: {
fetchTickets() {
fetch('/api')
.then(res => res.json())
.then(resJson => {
arrayLength = resJson.length
for(var i = 0; i < arrayLength; i++) {
if(resJson[i]['status'] === 'active') {
//do something
}
else if(resJson[i]['status'] === 'waiting on customer') {
// do something else
}
else {
// do a dance
}
}
});
},
}
So, is it okay to do the above or is it very sensitive to errors/is there a more convenient alternative?
There is a more convient alternative.
You should create two API calls.
1.) /api/activeUsers
2.) /api/waitingCustomers
Then for each API call, you can use the .filter API and return the appropiate array
fetchActiveTickets() {
fetch('/api')
.then(res => res.json())
.then(resJson => {
return resJson.filter(item => {
return item.status ==='active'
})
//do the same for waiting... i.e. resJson(item => {
//return item.status ==='waiting'
//})
}
});
},
I would recommend using .filter() rather than looping over the array to split the source into the pieces you want.
data: {
activeTickets: [],
waitingTickets: []
}
methods: {
fetchTickets() {
fetch('/api')
.then(res => res.json())
.then(resJson => {
this.activeTickets = resJson.filter(function(ticket) { return ticket.status === 'active' });
this.waitingTickets= resJson.filter(function(ticket) { return ticket.status === 'waiting on customer' });
// do things with your filters arrays...
});
},
}
Try
methods: {
async fetchTickets() {
let res = await (await fetch('/api')).json();
let active = res.filter(x=> x['status']=='active');
let waiting = res.filter(x=> x['status']=='waiting on customer');
// ... do something
},
}
I followed the example from official docs, here is how to implement multiselection feature:
state = { selected: (new Map(): Map<string, boolean>) };
onPressItem = (id) => {
this.setState((state) => {
const selected = new Map(state.selected);
selected.set(id, !selected.get(id));
return { selected };
});
};
I'm struggling with making it single select though. It's easy to return new Map with false values anytime cell is tapped, but that means the cell cannot be deselected by another tap on it, which is the desired feature in my case.
onPressItem = (id) => {
this.setState((state) => {
const selected = new Map();
selected.set(id, !selected.get(id));
return { selected };
});
};
How would you implement it? Should I use lodash to iterate over the Map to find the one that already is true and change its value (now sure how to iterate over Map though), or maybe there is some better approach I am missing right now?
EDIT
Iterating over elements of the selected Map seems to be a really ugly idea, but it is simple and it actually works. Is there any better way to do it that I am missing out on?
onPressItem = (id: string) => {
this.setState((state) => {
const selected = new Map(state.selected);
selected.set(id, !selected.get(id));
for (const key of selected.keys()) {
if (key !== id) {
selected.set(key, false);
}
}
return { selected };
});
};
Thanks in advance
You can just set only one value instead of a map like this
onPressItem = (id) => {
this.setState((state) => {
const selected = selected === id ? null : id;
return { selected };
});
};
I had the same issue, my solution was:
_onPressItem = (id: string) => {
// updater functions are preferred for transactional updates
this.setState((state) => {
// copy the map rather than modifying state.
const selected = new Map(state.selected);
// save selected value
let isSelected = selected.get(id);
// reset all to false
selected.forEach((value, key) => {
selected.set(key, false);
});
// then only activate the selected
selected.set(id, !isSelected);
return { selected };
});
};
I am trying to implement an infinite scroll in React Native. Below is the source of the component:
var React = require('react-native');
var server = require('../server');
var Post = require('./Post');
var SwipeRefreshLayoutAndroid = require('./SwipeRefreshLayout');
var backEvent = null;
var lastPostId = "";
var isLoadingMore = false;
var isLoadingTop = false;
var onEndReachedActive = false;
var {
StyleSheet,
ListView,
View,
Text,
Image,
ProgressBarAndroid,
BackAndroid
} = React;
class Stream extends React.Component {
constructor(props) {
super(props);
this.ds = new ListView.DataSource({
rowHasChanged: (row1, row2) => {
console.log("rowHasChenged FIRED!!");
return false;
}
});
this.state = {
dataSource: this.ds.cloneWithRows(['loader']),
hasStream: false,
posts: []
};
}
componentDidMount() {
BackAndroid.addEventListener('hardwareBackPress', () => {
this.props.navigator.jumpBack();
return true;
}.bind(this));
server.getStream('', '', 15).then((res) => {
lastPostId = res[res.length-1].m._id;
this.setState({
posts: res,
hasStream: true,
dataSource: this.ds.cloneWithRows(res)
}, () => onEndReachedActive = true);
})
}
onRefresh() {
var posts = this.state.posts;
var firstPost = posts[0].m._id;
console.log(this.state.dataSource._rowHasChanged);
isLoadingTop = true;
server.getStream('', firstPost, 4000)
.then(res => {
console.log(posts.length);
posts = res.concat(posts);
console.log(posts.length);
this.setState({
dataSource: this.ds.cloneWithRows(posts),
posts
}, () => {
this.swipeRefreshLayout && this.swipeRefreshLayout.finishRefresh();
isLoadingTop = false;
});
}).catch((err) => {
isLoadingTop = false;
})
}
onEndReached(event) {
if(!onEndReachedActive) return;
if(this.state.loadingMore || this.state.isLoadingTop)return;
isLoadingMore = true;
var posts = this.state.posts;
server.getStream(posts[posts.length-1].m._id, '', 15)
.then(res => {
console.log('received posts');
posts = posts.concat(res);
lastPostId = posts[posts.length-1].m._id;
this.setState({
dataSource: this.ds.cloneWithRows(posts),
posts
}, ()=>isLoadingMore = false);
})
}
renderHeader() {
return (
<View style={styles.header}>
<Text style={styles.headerText}>Header</Text>
</View>
)
}
renderRow(post) {
if(post === 'loader') {
return (
<ProgressBarAndroid
styleAttr="Large"
style={styles.spinnerBottom}/>
)
}
let hasLoader = post.m._id === lastPostId;
let loader = hasLoader ?
<ProgressBarAndroid
styleAttr="Large"
style={styles.spinnerBottom}/> : null;
return (
<View>
<Post
post={post}/>
{loader}
</View>
)
}
render() {
return (
<ListView
style={styles.mainContainer}
dataSource={this.state.dataSource}
renderRow={this.renderRow.bind(this)}
onEndReached={this.onEndReached.bind(this)}
onEndReachedThreshold={1}
pageSize={15} />
);
}
}
The problem is that whenever I append (or prepend) new data, the rowHasChanged method of the DataSource doesn't fire. It just re-renders every row, even tho nothing has changed (except the new data).
Any idea why the method is bypassed?
Edit: Pass a function to setState to avoid race conditions
I just figured it out. If you are having the same issue, check the point at which you change your state with the new dataSource. Mine was like this:
this.setState({
dataSource: this.ds.cloneWithRows(posts)
});
Instead you should always use the dataSource from the previous state, like this:
this.setState(state => ({
dataSource: state.dataSource.cloneWithRows(posts)
}))
Cheers!
this worked for me, hope this helps. I created a new dataSource and assigned the updated data to it on state change as follows:`
var dataSource = new ListView.DataSource(
{rowHasChanged: (r1, r2) => ( r1 !== r2)});
this.setState({ dataSource : dataSource.cloneWithRows(posts) });
Now, the new data is assigned and the view is rendered correctly. Note that posts array that is assigned now holds the updated data. Still wondering though if it's the best way to do it but it works!
I agree it seems to make sense that you should always use the dataSource from the previous state.
Yet when I setState this way, rowHasChanged gets called for all rows, however, rowHasChanged always returns false and no rows are rendered??? Why?
// This is callback handler that the ListView DetailView will
// call when a ListView item is edited
onChange(waypoint: Object){
console.log('Callback: rowNumber= ', waypoint.rowNumber);
console.log(' length(m)= ', waypoint.distance.meters);
var itemListChanged = this.state.itemList;
itemListChanged[waypoint.rowNumber-1] = waypoint;
this.setState({
dataSource: this.state.dataSource.cloneWithRows(itemListChanged),
});
},
If I setState this way, renderRow is called for all rows unconditionally without rowHasChanged ever being called. Which is correct?
this.setState({
dataSource: ds.cloneWithRows(itemListChanged),
});
ListView, datasource, and react-native are a hard learning curve coming from C#/C/C++.
for anyone still having issue with rowHasChanged called but are still returning false the following snippets might help
the datasource is initialized like usual:
let ds = new ListView.DataSource ({
rowHasChanged: (a, b) => {
const changed = (a !== b)
return changed
}
})
this.data = []
this.state = {
listDataSource: ds.cloneWithRows(this.data)
}
here is the function which will update a row
updateRow = (row, rowId, sectionId) => {
// make a shallow clone from the stored data array
let blob = this.data.concat()
// modify the row, since we are using the triple equal operator, we need to make sure we are giving it a new object (new ref)
blob[rowId] = Object.assign({}, blob[rowId], {label: blob[rowId].label + '..cape..deh'})
// tell react to update the source
this.setState({
listDataSource: this.state.listDataSource.cloneWithRows(blob)
}, () => {
// we need to update our data storage here! after the state is changed
this.data = blob
})
}