I've a problem integrating the Firebase with React-Native. The code below doesn't generate a listview as I expected. My assumption is that messages.val() doesn't return a correct format. When I try to console log "messages" variable it returns as follow
Object {text: "hello world", user_id: 1}
Code :
class Test extends Component {
constructor(props) {
super(props);
this.state = {
dataSource: new ListView.DataSource({
rowHasChanged: (row1, row2) => row1 !== row2
})
};
}
componentWillMount() {
this.dataRef = new Firebase("https://dummy.firebaseio.com/");
this.dataRef.on('child_added', function(snapshot){
var messages = snapshot.val();
this.setState({
dataSource: this.state.dataSource.cloneWithRows(messages)
});
}.bind(this));
}
renderRow(rowData, sectionID, rowID) {
console.log(this.state.dataSource);
return (
<TouchableHighlight
underlayColor='#dddddd'>
<View>
<Text>{rowData.user_id}</Text>
<Text>{rowData.text}</Text>
</View>
</TouchableHighlight>
)
}
render() {
return (
<View>
<ListView
dataSource={this.state.dataSource}
renderRow={this.renderRow.bind(this)}
automaticallyAdjustContentInsets={false} />
</View>
);
}
}
I do not know what data you have in your Firebase database, but from what I understand, you should get multiple "on_child_added" events for all items you have, so you should not pass it to "cloneWithRows" method. You should pass the whole dataset to it.
While the documentation on react native side is a bit "silent" currently about how the ListView data source works and what should be passed to "cloneWithRows", documentation in the code (ListViewDataSource.js) is pretty good in fact, and it's explicit, that you should always provide full data set to "cloneWithRows" method (similarly to view reconciliation, the datasource will automatically calculate the difference and only modify the data that has actually changed).
Also, there is a very good write-up by #vjeux on why they implemented ListView the way they did, including explaining the optimisation strategies they chose (different than iOS's UITableView).
So in your case you should rather accumulate all the rows somewhere else and only pass the whole array of messages to cloneWithRows or relay on the incremental behaviour of cloneWithRows and continuously append the incoming elements to cloneWithRows as they come as in below example (it's supposed to be fast so give it a try).
The documentation copy&paste from ListViewDataSource.js:
/**
* Provides efficient data processing and access to the
* `ListView` component. A `ListViewDataSource` is created with functions for
* extracting data from the input blob, and comparing elements (with default
* implementations for convenience). The input blob can be as simple as an
* array of strings, or an object with rows nested inside section objects.
*
* To update the data in the datasource, use `cloneWithRows` (or
* `cloneWithRowsAndSections` if you care about sections). The data in the
* data source is immutable, so you can't modify it directly. The clone methods
* suck in the new data and compute a diff for each row so ListView knows
* whether to re-render it or not.
*
* In this example, a component receives data in chunks, handled by
* `_onDataArrived`, which concats the new data onto the old data and updates the
* data source. We use `concat` to create a new array - mutating `this._data`,
* e.g. with `this._data.push(newRowData)`, would be an error. `_rowHasChanged`
* understands the shape of the row data and knows how to efficiently compare
* it.
*
* ```
* getInitialState: function() {
* var ds = new ListViewDataSource({rowHasChanged: this._rowHasChanged});
* return {ds};
* },
* _onDataArrived(newData) {
* this._data = this._data.concat(newData);
* this.setState({
* ds: this.state.ds.cloneWithRows(this._data)
* });
* }
* ```
*/
Related
in react, I have a list of topics, each with a nested list of sets. The topics render the length of sets, and the sets. Occasionally I need to move a set from one topic to another. I can get the sets to re-render after moving correctly, but the set length won't visually change. I believe this is an issue with mutating the state, but I believe I compensate for that my copying my topics array with this.state.Topics.slice() before any changes to it.
TopicList.js
ChangeSetsTopic(event)
{
var sourceTopic;
var topics = this.state.Topics.slice();
sourceTopic = topics.find(obj => {return obj.Topic == event.source.droppableId});
var change_set;
for (var i = sourceTopic["Sets"].length - 1; i >= 0; --i) {
if (sourceTopic["Sets"][i].SetID == event.draggableId) {
change_set = sourceTopic["Sets"].splice(i,1)[0];
}
}
var destinationTopic = topics.find(obj => {return obj.Topic == event.destination.droppableId});
destinationTopic["Sets"].push(change_set);
for (var i = 0 ; i < topics.length ; i++) {
if (topics[i].Topic == sourceTopic.Topic) {
topics[i] = sourceTopic;
}
else if (topics[i].Topic == destinationTopic.Topic) {
topics[i] = destinationTopic;
}
}
this.setState({"Topics" : topics });
}
render(){
return( {this.state.Topics.map(( topic , key ) => (
<Topic key={topic.Topic} Sets={topic.Sets} TopicName={topic.Topic}/>
))}
);}
Topic.js
componentDidUpdate(prevProps) {
if(prevProps.Sets.length.toString() != this.props.Sets.toString() )
{
this.setState({"Sets" : this.props.Sets })
}
}
render(){
return( ... <div>this.state.Sets.length.toString()</div> ...)
}
React has no idea that by calling destinationTopic["Sets"].push(change_set); mutates the state of another component. It is recommended that your components be as stateless as possible and that your data come from a single source that then propagates down your chain. I'd recommend looking into the Flux architecture, as this will help you better understand how data flows through React without internal state. One implementation of Flux is Redux
If you are set on mutating state the way you are, then I would recommend looking into Mobx since what you have will work out of the box using Mobx stores.
Good luck
Topics array changing and React can see it updated, but it can't see individual items in array updated, items are objects, React compares objects by reference, not by content, so it can't see changes there.
E.g. destinationTopic is an object.
When I create new note, it orders old to new, but I want to order new to old (reverse it).
How can i do this ?
my codes:
const notesRef = useFirestore().collection('users').doc(uid).collection("notes");
const {status, data} = useFirestoreCollection(notesRef.orderBy("timezone"));
and its image: (Here, it order like 1-2-3, but i want to order, 3-2-1, new to old)
our return like map:
{data?.docs?.map((d, index) => {
return (<Note
key={index}
id={index}
title={d.data().title}
content={d.data().content}
onDelete={deleteNote}
onDocId={d.id}
timezone={d.data().timezone}
/>);
})}
To sort a query in descending order, you can pass a second parameter to orderBy. So:
notesRef.orderBy("timezone", "desc")
Also see the Firebase documentation on ordering data.
There are a bunch of ways of doing this.
Fetch the data in reversed order from firebase. I am using 25 as an example. You can choose the limit as per your requirements.
notesRef.orderBy("timezone").limitToLast(25);
Reverse the data on the client-side
const {status, data} = useFirestoreCollection(notesRef.orderBy("timezone"));
console.log(data.docs.reverse())
I just started learning RxJS. One thing I have tried to do without much luck is, figuring out how to search/filter a Subject, or create an observed array that I can search on.
I've tried piping and filtering a Subject and BehaviorSubject, but the values in the predicate are RxJS specific.
From what I've read on various posts, the way is to observe an array to use a Subject.
I can easily have two variables, one array and the Subject, and search the array. But I'd like to accomplish this one variable.
In Knockout its possible to search an observed array.
Is this possible in RxJS?
Thanks in advance.
Example:
layers: Rx.Subject<any> = new Rx.Subject<any>();
toggleLayer (layerId, visible) {
//find layer we need to toggle
// How do I search the subject to get the values added in next()?
// tried from(this.layers), pipe does not fire
const source = of(this.layers);
const example = source.pipe(filter((val, index) => {
//val is subject, and only iterates once, even if more than one value in subject
// tslint:disable-next-line:no-debugger
debugger;
return false;
}));
const sub = example.subscribe((val) => {
// tslint:disable-next-line:no-debugger
debugger;
});
}
private addLayer = (layerName, layerObj, layerType) => {
// component class is subscribed to layers subject. Update UI when layer is added
this.layers.next({
layerId: this.layerId,
name: `${layerName}_${this.layerId}`,
layerObj: layerObj,
visible: true,
layerType: layerType
});
}
I'm not 100% clear on the specifics of your ask, but maybe this example will help you.
const filterSubject = new BehaviorSubject<string>('b');
const dataSubject = new BehaviorSubject<string[]>(['foo', 'bar', 'baz', 'bat']);
const dataObservable = combineLatest(filterSubject, dataSubject).pipe(
// given an array of values in the order the observables were presented
map(([filterVal, data]) => data.filter(d => d.indexOf(filterVal) >= 0))
);
dataObservable.subscribe(arr => console.log(arr.join(',')));
// bar, baz, bat
Using combineLatest, you can have the value in dataObservable updated whenever either your filter value or your data array changes.
I have a component that needs strings from the backend. I currently request the .po file from the server, convert it to .json and return it to my React component. I then want to be able to display those strings whilst replacing the correct values in the string, i.e.
<FormattedMessage id={dynamicId} values={dynamicVals} />
dynamicId is pulled from a separate api call, as well as dynamicVals.
My problem is that these strings are not bundled like all of my other app strings, so react-intl is unaware of them. How can I add these strings to the library client-side/async? I've attempted using defineMessages and addLocaleData, but I either am doing something incorrectly, or am not using the right api methods. Does addLocaleData provide the means to adding strings to the library? Is this possible to do?
In summary:
How can I receive
{
notifications.friendships.nowfriends: "{name} is now your friend"
}
from the api and display it using:
<FormattedMessage id='notifications.friendships.nowfriends' values={{ name: 'StackOver Bro' }} />
Thanks for the help in advance.
In case anyone wants to know what I ended up doing...
Since I already had the strings and the variables to interpolate with, I bypassed the localization library and just used the following function (thanks to the answers on this question, especially #user2501097 and #Bryan Rayner)
/**
* see: https://stackoverflow.com/questions/29182244/convert-a-string-to-a-template-string
*
* Produces a function which uses template strings to do simple interpolation from objects.
*
* Usage:
* var makeMeKing = generateTemplateString('${name} is now the king of {country}!');
*
* console.log(makeMeKing({ name: 'Bryan', country: 'Scotland'}));
* // Logs 'Bryan is now the king of Scotland!'
*/
const generateTemplateString = (function () {
const cache = {}
function generateTemplate(template) {
let fn = cache[template]
if (!fn) {
// Replace ${expressions} (etc) with ${map.expressions}.
const sanitized = template
.replace(/\$?\{([\s]*[^;\s\{]+[\s]*)\}/g, (_, match) => {
return `\$\{map.${match.trim()}\}`
})
// Afterwards, replace anything that's not ${map.expressions}' (etc) with a blank string.
.replace(/(\$\{(?!map\.)[^}]+\})/g, '')
fn = Function('map', `return \`${sanitized}\``)
cache[template] = fn
}
return fn
}
return generateTemplate
}())
export default generateTemplateString
I have been experimenting with RxJS for two weeks now, and although I love it in principle I just cannot seem to find and implement the correct pattern for managing state. All articles and questions appear to agree:
Subject should be avoided where possible in favor of just pushing state through via transformations;
.getValue() should be deprecated entirely; and
.do should perhaps be avoided except for DOM manipulation?
The problem with all such suggestions is that none of the literature appears to directly say what you should be using instead, besides "you'll learn the Rx way and stop using Subject".
But I cannot find a direct example anywhere that specifically indicates the correct way to perform both additions and removals to a single stream/object, as the consequence of multiple other stream inputs, in a stateless and functional manner.
Before I get pointed in the same directions again, problems with uncovered literature are:
The Introduction to Reactive Programming You've been missing: great starting text, but does not specifically address these questions.
The TODO example for RxJS comes with React and involves explicit manipulation of Subjects as proxies for React Stores.
http://blog.edanschwartz.com/2015/09/18/dead-simple-rxjs-todo-list/ : explicitly uses a state object for addition and removal of items.
My perhaps 10th rewrite of the standard TODO follows - My prior iterations covered include:
starting with a mutable 'items' array - bad as state is explicit and imperatively managed
using scan to concatenate new items to an addedItems$ stream, then branching another stream where the removed items were deleted - bad as the addedItems$ stream would grow indefinitely.
discovering BehaviorSubjectand using that - seemed bad since for each new updatedList$.next() emission, it requires the previous value to iterate, meaning that Subject.getValue() is essential.
trying to stream the result of the inputEnter$ addition events into filtered removal events - but then every new stream creates a new list, and then feeding that into the toggleItem$ and toggleAll$ streams means that each new stream is dependent on the previous, and so causing one of the 4 actions (add, remove, toggle item or toggle all) requires the whole chain to be unnecessarily run through again.
Now I have come full circle, where I am back to using both Subject (and just how is it supposed to be successively iterated upon in any way without using getValue()?) and do, as show below. Myself and my colleague agree this is the clearest way, yet it of course seems the least reactive and most imperative. Any clear suggestions on the correct way for this would be much appreciated!
import Rx from 'rxjs/Rx';
import h from 'virtual-dom/h';
import diff from 'virtual-dom/diff';
import patch from 'virtual-dom/patch';
const todoListContainer = document.querySelector('#todo-items-container');
const newTodoInput = document.querySelector('#new-todo');
const todoMain = document.querySelector('#main');
const todoFooter = document.querySelector('#footer');
const inputToggleAll = document.querySelector('#toggle-all');
const ENTER_KEY = 13;
// INTENTS
const inputEnter$ = Rx.Observable.fromEvent(newTodoInput, 'keyup')
.filter(event => event.keyCode === ENTER_KEY)
.map(event => event.target.value)
.filter(value => value.trim().length)
.map(value => {
return { label: value, completed: false };
});
const inputItemClick$ = Rx.Observable.fromEvent(todoListContainer, 'click');
const inputToggleAll$ = Rx.Observable.fromEvent(inputToggleAll, 'click')
.map(event => event.target.checked);
const inputToggleItem$ = inputItemClick$
.filter(event => event.target.classList.contains('toggle'))
.map((event) => {
return {
label: event.target.nextElementSibling.innerText.trim(),
completed: event.target.checked,
};
})
const inputDoubleClick$ = Rx.Observable.fromEvent(todoListContainer, 'dblclick')
.filter(event => event.target.tagName === 'LABEL')
.do((event) => {
event.target.parentElement.classList.toggle('editing');
})
.map(event => event.target.innerText.trim());
const inputClickDelete$ = inputItemClick$
.filter(event => event.target.classList.contains('destroy'))
.map((event) => {
return { label: event.target.previousElementSibling.innerText.trim(), completed: false };
});
const list$ = new Rx.BehaviorSubject([]);
// MODEL / OPERATIONS
const addItem$ = inputEnter$
.do((item) => {
inputToggleAll.checked = false;
list$.next(list$.getValue().concat(item));
});
const removeItem$ = inputClickDelete$
.do((removeItem) => {
list$.next(list$.getValue().filter(item => item.label !== removeItem.label));
});
const toggleAll$ = inputToggleAll$
.do((allComplete) => {
list$.next(toggleAllComplete(list$.getValue(), allComplete));
});
function toggleAllComplete(arr, allComplete) {
inputToggleAll.checked = allComplete;
return arr.map((item) =>
({ label: item.label, completed: allComplete }));
}
const toggleItem$ = inputToggleItem$
.do((toggleItem) => {
let allComplete = toggleItem.completed;
let noneComplete = !toggleItem.completed;
const list = list$.getValue().map(item => {
if (item.label === toggleItem.label) {
item.completed = toggleItem.completed;
}
if (allComplete && !item.completed) {
allComplete = false;
}
if (noneComplete && item.completed) {
noneComplete = false;
}
return item;
});
if (allComplete) {
list$.next(toggleAllComplete(list, true));
return;
}
if (noneComplete) {
list$.next(toggleAllComplete(list, false));
return;
}
list$.next(list);
});
// subscribe to all the events that cause the proxy list$ subject array to be updated
Rx.Observable.merge(addItem$, removeItem$, toggleAll$, toggleItem$).subscribe();
list$.subscribe((list) => {
// DOM side-effects based on list size
todoFooter.style.visibility = todoMain.style.visibility =
(list.length) ? 'visible' : 'hidden';
newTodoInput.value = '';
});
// RENDERING
const tree$ = list$
.map(newList => renderList(newList));
const patches$ = tree$
.bufferCount(2, 1)
.map(([oldTree, newTree]) => diff(oldTree, newTree));
const todoList$ = patches$.startWith(document.querySelector('#todo-list'))
.scan((rootNode, patches) => patch(rootNode, patches));
todoList$.subscribe();
function renderList(arr, allComplete) {
return h('ul#todo-list', arr.map(val =>
h('li', {
className: (val.completed) ? 'completed' : null,
}, [h('input', {
className: 'toggle',
type: 'checkbox',
checked: val.completed,
}), h('label', val.label),
h('button', { className: 'destroy' }),
])));
}
Edit
In relation to #user3743222 very helpful answer, I can see how representing state as an additional input can make a function pure and thus scan is the best way to represent a collection evolving over time, with a snapshot of its previous state up to that point as an additional function parameter.
However, this was already how I approached my second attempt, with addedItems$ being a scanned stream of inputs:
// this list will now grow infinitely, because nothing is ever removed from it at the same time as concatenation?
const listWithItemsAdded$ = inputEnter$
.startWith([])
.scan((list, addItem) => list.concat(addItem));
const listWithItemsAddedAndRemoved$ = inputClickDelete$.withLatestFrom(listWithItemsAdded$)
.scan((list, removeItem) => list.filter(item => item !== removeItem));
// Now I have to always work from the previous list, to get the incorporated amendments...
const listWithItemsAddedAndRemovedAndToggled$ = inputToggleItem$.withLatestFrom(listWithItemsAddedAndRemoved$)
.map((item, list) => {
if (item.checked === true) {
//etc
}
})
// ... and have the event triggering a bunch of previous inputs it may have nothing to do with.
// and so if I have 400 inputs it appears at this stage to still run all the previous functions every time -any- input
// changes, even if I just want to change one small part of state
const n$ = nminus1$.scan...
The obvious solution would be to just have items = [], and manipulate it directly, or const items = new BehaviorSubject([]) - but then the only way to iterate on it appears to be using getValue to expose the previous state, which Andre Stalz (CycleJS) has commented on in the RxJS issues as something that shouldn't really be exposed (but again, if not, then how is it usable?).
I guess I just had an idea that with streams, you weren't supposed to use Subjects or represent anything via a state 'meatball', and in the first answer I'm not sure how this doesn't introduce mass chained streams which are orphaned/grow infinitely/have to build on each other in exact sequence.
I think you already found a good example with : http://jsbin.com/redeko/edit?js,output.
You take issue with the fact that this implementation
explicitly uses a state object for addition and removal of items.
However, thas is exactly the good practice you are looking for. If you rename that state object viewModel for example, it might be more apparent to you.
So what is state?
There will be other definitions but I like to think of state as follows:
given f an impure function, i.e. output = f(input), such that you can have different outputs for the same input, the state associated to that function (when it exists) is the extra variable such that f(input) = output = g(input, state) holds and g is a pure function.
So if the function here is to match an object representing a user input, to an array of todo, and if I click add on a todo list with already have 2 todos, the output will be 3 todos. If I do the same (same input) on a todo list with only one todo, the output will be 2 todos. So same input, different outputs.
The state here that allows to transform that function into a pure function is the current value of the todo array. So my input becomes an add click, AND the current todo array, passed through a function g which give a new todo array with a new todo list. That function g is pure. So f is implemented in a stateless way by making its previously hidden state explicit in g.
And that fits well with functional programming which revolves around composing pure functions.
Rxjs operators
scan
So when it comes to state management, with RxJS or else, a good practice is to make state explicit to manipulate it.
If you turn the output = g(input, state) into a stream, you get On+1 = g(In+1, Sn) and that's exactly what the scan operator does.
expand
Another operator which generalizes scan is expand, but so far I had very little use of that operator. scan generally does the trick.
Sorry for the long and mathy answer. It took me a while to get around those concepts and that's the way I made them understandable for me. Hopefully it works for you too.