How to save the onMove order to Core Data? - core-data

I'm storing a list of a custom data type in a Core Data container and looping through them as such:
ForEach(coreDataViewModel.favoriteVerses) { verse in
VStack(alignment: .leading, spacing: 4) {
Text(verse.verse)
}
}
// how to save this order if user closes out of app?
.onMove(perform: { indices, newOffset in
coreDataViewModel.move(indices: indices, newOffset: newOffset)
})
The move functionality works fine, but after clicking done and exiting out of the app, the order reverts back to the original state.
// CoreDataViewModel
func move(indices: IndexSet, newOffset: Int) {
favoriteVerses.move(fromOffsets: indices, toOffset: newOffset)
}

Related

How to show .overlay with search results full width

var navbody: some View {
NavigationView {
ZStack {
somedarkcolorhere
List(searchModel.suggestions ?? [], rowContent: { text in
NavigationLink(destination: MediaSearchResultsView(searchText: text)) {
Text(text)
}
})
.overlay(SearchMediaHintsResultsScreen(searchModel: searchModel))
.searchable(text: $searchModel.searchText
// https://stackoverflow.com/questions/69668266/searchable-modifier-not-displaying-search-bar-below-navigation-bar-title
/* uncomment for search field to be shown initially and ever.
On iPad running 16.1 search field does show initially.
On iphone running 15.6.1 navigationTitle shows and search field
initially does not
*/
// , placement: .navigationBarDrawer(displayMode: .always)
)
.navigationTitle("v1_what_are_we_searching_for".localized)
}
.onChange(of: searchModel.searchText) { _ in
searchModel.processChangeOfSearchText()
}
.preference(key: ErrorPreferenceKey.self, value: observableError)
.sheet(isPresented: $observableError.showingError) {
ErrorView(error: observableError)
}
}
}
How to show .overlay with search results full width of the window?
currently it has gaps on the left and right
It is hard to see from just the code you have shown.
The List has default styling that includes some padding round the edges. You could try using a ForEach, or change the list style with a listStyle(.plain) modifier.
Does your overlay view also have a frame set to occupy the available space, such as .frame(maxWidth: .infinity)?
It may be due to the List default ListStyle of .insetGrouped. Try using .plain
Or try adding another view to the top layer of the ZStack that is only there when you want to show the overlay, so that it doesn't block List tap gestures, but can use the full View bounds to show the overlay.
i.e.
ZStack {
somedarkcolorhere
List { }
if showOverlay { Overlay }
}

Dynamic Table updates "too late" ReactJS

my problem is, that I have a table which should update everytime when the user chooses something from a dropdown component. The problem now is that my table updates "too late" in the frontend. So when the user chooses an option for the first time nothing will happen. Then when the user chooses an option for the second time from the dropdown component, the table will show the data from the option he has picked before. If the user chooses an option for the 3rd time, the table will show the data from the second one and so on.
So how can I fix this? I work with ReactJS and Semantic UI
My Code:
This renders the Row for the existing data
renderTableData() {
return this.state.songs.map((song, index) => {
const { id, nr, songname, link } = song
return (
<Table.Row key={id}>
<Table.Cell>{nr}</Table.Cell>
<Table.Cell>{songname}</Table.Cell>
<Table.Cell>{link}</Table.Cell>
</Table.Row>
)
})
}
The Code in the main render() function of React (Its shown correctly, expect that the data is "outdated":
`<Table>
<Table.Header>
<Table.Row>
<Table.HeaderCell width={1}>Nr</Table.HeaderCell>
<Table.HeaderCell width={2}>Songname</Table.HeaderCell>
<Table.HeaderCell width={1}>Link</Table.HeaderCell>
</Table.Row>
</Table.Header>
{this.renderTableData()}
</Table>`
The code when the option from the dropdown gets changed:
onChangeDropdown(e) {
this.setState({game: e.target.textContent}, ()=>{
this.state.songs.length = 0;
for(var i = 0; i< this.state.musicData.length;i++){
if(this.state.musicData[i].game == this.state.game){
for(var j = 0; j<this.state.musicData[i].songs.length;j++){
this.state.songs.push({id: j+1, nr: j+1, songname: this.state.musicData[i].songs[j].name, link: this.state.musicData[i].songs[j].link})
}
break;
}
}
this.renderTableData()
})
}
The game variable in this.setState is correct and also the for-loop works as expected when the user changes the dropdown option, I already checked it with the debugger
I hope you can help me out there, ty
is not that is updating too late, is that you are mutating the state without using setState so React doesn't know what changed, you should refactor your code to always use setState to update the state, not push, something like this:
onChangeDropdown(e) {
this.setState((currentState) => {
const newSongs = [];
const game = e.target.textContent;
musicData.forEach((data) => {
if (data.game === game) {
musicData.songs.forEach((song, index) => {
newSongs.push({
id: index + 1,
nr: index + 1,
songname: song.name,
link: song.link,
});
});
}
});
return {
...currentState,
game,
songs: newSongs,
};
});
}
I changed your for loops to use forEach, less complexity, easier to read
Here is what I did:
create a empty array to store the selected songs (newSongs)
loop all the music data and then loop all the songs inside each item in music data
add the songs from the selected game into newSongs
return newSongs + game to update the selected game, ...currentState is to preserve the other parts of the state between changes
So every time the dropodown changes, I create a new array and run the logic
The setState callback can return an object to replace whole state, so before that you can do any calculation you need to.
Updating the state in React is asyncronous, that's one of the reasons you can't mutate the state directly and need to use setState any time you need to update it

Redux Toolkit - Slice utility methods

I'm building a React app with redux-toolkit and I'm splitting my store into some slices with redux-toolkit's helper function createSlice.
Here it is a simple use case:
const sidebar = createSlice({
name: "sidebar",
initialState:
{
menus: {}, // Keep track of menus states (guid <-> open/close)
visible: true
},
reducers:
{
show(state, action)
{
state.visible = action.payload.visible;
},
setMenuOpen(state, action)
{
const { id, open } = action.payload;
state.menus[id] = open;
return state;
}
}
});
export default sidebar;
Everything works fine until I "add" actions (that change the store) to the slice but consider your team looking for an utility function "getMenuOpen": this method doesn't change the store (it's not an action and cannot be addeded to reducers object). You can of course read directly the data from the store (state.menus[<your_id>]) but consider a more complex example where manipulating the data requires some library imports, more complex code, etc...I want to modularize/hide each slice as much as possible.
Actually I'm using this workaround:
const sidebar = createSlice({ /* Same previous code... */ });
sidebar.methods =
{
getMenuOpen: (state, id) => state.menus[id]
};
export default sidebar;
The above code allows importing the slice from a component, mapStateToProps to the redux store, and invoke the utilty function getMenuOpen like this:
import sidebar from "./Sidebar.slice";
// Component declaration ...
const mapStateToProps = state => ({
sidebar: state.ui.layout.sidebar,
getMenuOpen(id)
{
return sidebar.methods.getMenuOpen(this.sidebar, id);
}
});
const mapDispatchToProps = dispatch => ({
setMenuOpen: (id, open) => dispatch(sidebar.actions.setMenuOpen({id, open}))
});
The ugly part is that I need to inject the slice node (this.sidebar) as fist param of getMenuOpen because it's not mapped (as for actions with reducers/actions) automatically from redux-toolkit.
So my question is: how can I clean my workaround in order to automatically map the store for utility functions? createSlice doesn't seem to support that but maybe some internal redux's api could help me in mapping my "slice.methods" automatically to the store.
Thanks

React Native: Reach-Navigation and Pouch-DB - db.put not done before "refresh" callback is run

Relative newbie; forgive me if my etiquette and form here aren't great. I'm open to feedback.
I have used create-react-native-app to create an application using PouchDB (which I believe ultimately uses AsyncStorage) to store a list of "items" (basically).
Within a TabNavigator (main app) I have a StackNavigator ("List screen") for the relevant portion of the app. It looks to the DB and queries for the items and then I .map() over each returned record to generate custom ListView-like components dynamically. If there are no records, it alternately displays a prompt telling the user so. In either case, there is an "Add Item" TouchableOpacity that takes them to a screen where they an add a new item (for which they are taken to an "Add" screen).
When navigating back from the "Add" screen I'm using a pattern discussed quite a bit here on SO in which I've passed a "refresh" function as a navigation param. Once the user uses a button on the "Add" screen to "save" the changes, it then does a db.post() and adds them item, runs the "refresh" function on the "List screen" and then navigates back like so:
<TouchableOpacity
style={styles.myButton}
onPress={() => {
if (this.state.itemBrand == '') {
Alert.alert(
'Missing Information',
'Please be sure to select a Brand',
[
{text: 'OK', onPress: () =>
console.log('OK pressed on AddItemScreen')},
],
{ cancelable: false }
)
} else {
this.createItem();
this.props.navigation.state.params.onGoBack();
this.props.navigation.navigate('ItemsScreen');
}
}
}
>
And all of this works fine. The "refresh" function (passed as onGoBack param) works fine... for this screen. The database is called with the query, the new entry is found and the components for the item renders up like a charm.
Each of the rendered ListItem-like components on the "List screen" contains a react-native-slideout with an "Edit" option. An onPress for these will send the user to an "Item Details" screen, and the selected item's _id from PouchDB is passed as a prop to the "Item Details" screen where loadItem() runs in componentDidMount and does a db.get(id) in the database module. Additional details are shown from a list of "events" property for that _id (which are objects, in an array) which render out into another bunch of ListItem-like components.
The problem arises when either choose to "Add" an event to the list for the item... or Delete it (using another function via [another] slideout for these items. There is a similar backward navigation, called in the same form as above after either of the two functions is called from the "Add Event" screen, this being the "Add" example:
async createEvent() {
var eventData = {
eventName: this.state.eventName.trim(),
eventSponsor: this.state.eventSponsor.trim(),
eventDate: this.state.eventDate,
eventJudge: this.state.eventJudge.trim(),
eventStandings: this.state.eventStandings.trim(),
eventPointsEarned: parseInt(this.state.eventPointsEarned.trim()),
};
var key = this.key;
var rev = this.rev;
await db.createEvent(key, rev, eventData);
}
which calls my "db_ops" module function:
exports.createEvent = function (id, rev, eventData) {
console.log('You called db.createEvent()');
db.get(id)
.then(function(doc) {
var arrWork = doc.events; //assign array of events to working variable
console.log('arrWork is first assigned: ' + arrWork);
arrWork.push(eventData);
console.log('then, arrWork was pushed and became: ' + arrWork);
var arrEvents = arrWork.sort((a,b)=>{
var dateA = new Date(a.eventDate), dateB = new Date(b.eventDate);
return b.eventDate - a.eventDate;
})
doc.events = arrEvents;
return db.put(doc);
})
.then((response) => {
console.log("db.createEvent() response was:\n" +
JSON.stringify(response));
})
.catch(function(err){
console.log("Error in db.createEvent():\n" + err);
});
}
After which the "Add Event" screen's button fires the above in similar sequence to the first, just before navigating back:
this.createEvent();
this.props.navigation.state.params.onGoBack();
this.props.navigation.navigate('ItemsDetails');
The "refresh" function looks like so (also called in componentDidMount):
loadItem() {
console.log('Someone called loadItem() with this.itemID of ' + this.itemID);
var id = this.itemID;
let totalWon = 0;
db.loadItem(id)
.then((item) => {
console.log('[LOAD ITEM] got back data of:\n' + JSON.stringify(item));
this.setState({objItem: item, events: item.events});
if (this.state.events.length != 0) { this.setState({itemLoaded: true});
this.state.events.map(function(event) {
totalWon += parseInt(event.eventPointsEarned);
console.log('totalWon is ' + totalWon + ' with ' +
event.eventPointsEarned + ' having been added.');
});
};
this.setState({totalWon: totalWon});
})
.catch((err) => {
console.log('db.loadItem() error: ' + err);
this.setState({itemLoaded: false});
});
}
I'm at a loss for why the List Screen refreshes when I add an item... but not when I'm doing other async db operations with PouchDB in what I think is similar fashion to modify the object containing the "event" information and then heading back to the Item Details screen.
Am I screwing up with Promise chain someplace? Neglecting behavior of the StackNavigator when navigating deeper?
The only other difference being that I'm manipulating the array in the db function in the non-working case, whereas the others I'm merely creating/posting or deleting/removing the record, etc. before going back to update state on the prior screen.
Edit to add, as per comments, going back to "List screen" and the opening "Item Details" does pull the database data and correctly shows that the update was made.
Further checking I've done also revealed that the console.log in createEvent() to print the response to the db call isn't logging until after some of the other dynamic rendering methods are getting called on the "Item Details" screen. So it seems as though the prior screen is doing the get() that loadItem() calls before the Promise chain in createEvent() is resolving. Whether the larger issue is due to state management is still unclear -- though it would make sense in some respects -- to me as this could be happening regardless of whether I've called my onGoBack() function.
Edit/bump: I’ve tried to put async/await to use in various places in both the db_ops module on the db.get() and the component-side loadItem() which calls it. There’s something in the timing of these that just doesn’t jive and I am just totally stuck here. Aside from trying out redux (which I think is overkill in this particular case), any ideas?
There is nothing to do with PDB or navigation, it's about how you manage outer changes in your depending (already mounted in Navigator since they are in history - it's important to understand - so componentDidMount isn't enough) components. If you don't use global state redux-alike management (as I do) the only way to let know depending component that it should update is passing corresponding props and checking if they were changed.
Like so:
//root.js
refreshEvents = ()=> { //pass it to DeleteView via screenProps
this.setState({time2refreshEvents: +new Date()}) //pass time2refreshEvents to EventList via screenProps
}
//DeleteView.js
//delete button...
onPress={db.deleteThing(thingID).then(()=> this.props.screenProps.refreshEvents())}
//EventList.js
...
constructor(props) {
super(props);
this.state = {
events: [],
noEvents: false,
ready: false,
time2refreshEvents: this.props.screenProps.time2refreshEvents,
}
}
static getDerivedStateFromProps(nextProps, currentState) {
if (nextProps.screenProps.time2refreshEvents !== currentState.time2refreshEvents ) {
return {time2refreshEvents : nextProps.screenProps.time2refreshEvents }
} else {
return null
}
}
componentDidMount() {
this._getEvents()
}
componentDidUpdate(prevProps, prevState) {
if (this.state.time2refreshEvents !== prevState.time2refreshEvents) {
this._getEvents()
}
}
_getEvents = ()=> {
//do stuff querying db and updating your list with actual data
}

How to safely select across channels where some may get concurrently closed?

While answering a question I attempted to implement a setup where the main thread joins the efforts of the CommonPool to execute a number of independent tasks in parallel (this is how java.util.streams operates).
I create as many actors as there are CommonPool threads, plus a channel for the main thread. The actors use rendezvous channels:
val resultChannel = Channel<Double>(UNLIMITED)
val poolComputeChannels = (1..commonPool().parallelism).map {
actor<Task>(CommonPool) {
for (task in channel) {
task.execute().also { resultChannel.send(it) }
}
}
}
val mainComputeChannel = Channel<Task>()
val allComputeChannels = poolComputeChannels + mainComputeChannel
This allows me to distribute the load by using a select expression to find an idle actor for each task:
select {
allComputeChannels.forEach { chan ->
chan.onSend(task) {}
}
}
So I send all the tasks and close the channels:
launch(CommonPool) {
jobs.forEach { task ->
select {
allComputeChannels.forEach { chan ->
chan.onSend(task) {}
}
}
}
allComputeChannels.forEach { it.close() }
}
Now I have to write the code for the main thread. Here I decided to serve both the mainComputeChannel, executing the tasks submitted to the main thread, and the resultChannel, accumulating the individual results into the final sum:
return runBlocking {
var completedCount = 0
var sum = 0.0
while (completedCount < NUM_TASKS) {
select<Unit> {
mainComputeChannel.onReceive { task ->
task.execute().also { resultChannel.send(it) }
}
resultChannel.onReceive { result ->
sum += result
completedCount++
}
}
}
resultChannel.close()
sum
}
This gives rise to the situation where mainComputeChannel may be closed from a CommonPool thread, but the resultChannel still needs serving. If the channel is closed, onReceive will throw an exception and onReceiveOrNull will immediately select with null. Neither option is acceptable. I didn't find a way to avoid registering the mainComputeChannel if it's closed, either. If I use if (!mainComputeChannel.isClosedForReceive), it will not be atomic with the registration call.
This leads me to my question: what would be a good idiom to select over channels where some may get closed by another thread while others are still live?
The kotlinx.coroutines library is currently missing a primitive to make it convenient. The outstanding proposal is to add receiveOrClose function and onReceiveOrClosed clause for select that would make writing code like this possible.
However, you will still have to manually track the fact that your mainComputeChannel was closed and stop selecting on it when it was. So, using a proposed onReceiveOrClosed clause you'll write something like this:
// outside of loop
var mainComputeChannelClosed = false
// inside loop
select<Unit> {
if (!mainComputeChannelClosed) {
mainComputeChannel.onReceiveOrClosed {
if (it.isClosed) mainComputeChannelClosed = true
else { /* do something with it */ }
}
}
// more clauses
}
See https://github.com/Kotlin/kotlinx.coroutines/issues/330 for details.
There are no proposals on the table to further simplify this kind of pattern.

Resources