Hello, and thank you for this amazing library.
Can you pls give some exampels when using observable.ref?
Why i need this?
I have 2 stores:
@observable user - deeply nested object@observable currentBlockWhen user click on fron on some block, he trigger an editorStore.setCurrentBlock action:
@action
setCurrentBlock(block) {
this.currentBlock=block;
}
Tha problem is - that all modifications that user do on the front - is maded on currentBlock, and as i understood from mobX manual - observable create a deep clone of object instead reference.
So if i want that all manipulations on currentBlock will affect the userStore.project.someBlock i need to make @observable currentBlock - reference to block.
How i can do it?
Thank for you help
@observable.ref currentBlock will make the field observable without trying to make it's value observable, therefore no need for cloning.
If you want to make clear, that block should be treated as reference and not like value, you can make it an instace of class - MobX doesn't convert objects with custom prototype, so ref wouldn't be needed. It will also help you realize, that you can't update object just by replacing it.
I would also try to think if one of those fields or both can't be computed. It's also good to ask yourself who owns the block object/instance and for example what should happen when it's deleted...how do I propagate this change to other stores?
@urugator , thank for reply, ill try this one: @observable.ref currentBlock
about second option: make it an instace of class - how i can achieve that, if currentUser is the object that i receive from the server. Or you mean by
@observable currentUser=new User(userObject)`
And about @computed - i`m also like this idea, but in this case make
@computed get currentBlock () {
return this.currenUser.project.block[blockId]
}
cant help me, because i need possibility to make changes for currentBlock properties
Or you mean
I meant new Block(user.block), but yes it can be applied to user or anything else...
because i need possibility to make changes for currentBlock properties
I don't follow... You can change block props as usual getCurrentBlock().something = "value" or better getCurrentBlock().setSomething("value")
If there are more blocks in the app than just the blocks in the currentUsers's project, then you may want to do something like this:
// Simplified BlockStore (holds actual block instances)
const blockStore = observable([{id:0},{id:1},{id:2}]) // array of actual blocks
// in User (holds ids of his blocks)
@computed get blocks () {
return this.blockStore.filter(block => this.project.blockIds.findIndex(id => id === block.id) !== -1)
}
// in EditorStore (holds id of current block)
@computed get currentBlock () {
return this.blockStore.find(block => block.id === this.currentBlockId);
}
It really depends on who owns the blocks themself - where the blocks came from? Who is the source of truth?
Ok, i`ll explain:
i receive from server the user object with structure like this:
user={
id:'userId',
project:{
id:'projectId',
pages:{
0:{
number:1,
status:'open',
sections:{
1:{
name:'header',
blocks:{
0:{
id:'blockId',
type:'blockType',
value:'blockValue',
style:{
color:'#fff',
backgroundColor:'#000'
}
},
1:{
id:'blockId',
type:'blockType',
value:'blockValue',
style:{
color:'#fff',
backgroundColor:'#000'
}
}
}
}
},
}
}
}
}
So as you can see - observable user object is the source of truth.
project contain many pages.
every page contain many sections.
and section contain many blocks.
Actually the user on front will deal with 2 block`s properties:
block.value
and
block.style.styleProp
Now, what i do is after that user on front select some page to edit,
in userStore:
@observable currentPage=selectedPage
in editor user see project page splitted by blocks - that mapped from currentPage.section.blocks
click on block is trigger editorStore action:
currentBlock=selectedBlock
and display to user editPanel of this block - this editPanel is display actual style data from editorStore.currentBlock.style
and any changes in editor trigger editorStore action
currentBlock.style[styleProp]=newValue
In fact - i see change that maided in the editPanel only (because he receive his data from the editorStore.currentBlock),
but page that diplay blocks is not updated.
So which approach is best for this situation?
So which approach is best for this situation?
The best one is the one which will work and cost the least, dunno which one is it :)
Maybe following may somehow help:
// For simplicifaction there are no sections
// Notice that everything depends on user
// if user is set everything updates accordingly
class App {
@observable user = null;
@observable.ref _currentPage = null; // or id
@observable.ref _currentBlock = null; // or id
@computed get pages() {
// TODO handle cases when user is undefined
return user.pages;
}
@computed get blocks() {
// TODO handle cases when currentPage is undefined
return this.currentPage.project.blocks;
}
@computed get currentPage() {
// TODO handle cases when user or currentPage are undefined
return this.user.project.pages.find(page => page === this._currentPage);
}
@computed get currentBlock() {
// TODO handle cases when current page is undefiend
return this.currentPage.blocks.find(block => block === this._currentBlock);
}
@action setUser() { /*TODO*/ };
@action setCurrentPage(page) { /*TODO*/ };
@action setCurrentBlock(block) { /*TODO*/ };
}
// You may want to decouple it a bit:
class App {
@observable user = null;
@observable.ref _currentPage = null; // or id
@computed get pages() {
return user.pages;
}
@computed get currentPage() {
// TODO handle cases when user or currentPage are undefined
const page = this.user.project.pages.find(page => page === this._currentPage);
return new Page(page);
}
@action setUser() { /*TODO*/ };
@action setCurrentPage(page) { /*TODO*/ };
}
class Page { // or PageEditor or whatever
@observable _currentBlock = null; // or id
constructor({ blocks }) {
this.blocks = blocks;
}
@computed get currentBlock() {
return this.blocks.find(block => block === this._currentBlock);
}
@action setCurrentBlock(block) { /*TODO*/ };
}
EDIT: added the refs modifiers (not actually needed see following comments)
Thank you, i`ll try this.
One more question:
@action setCurrentBlock(block)
will do the same thing - i mean it still be a deepClone of a block, and this is mean tha any changes in _currentBlock props will not affect the existing one in the user.
am i right?
Oh yea there must be a ref on these fields, otherwise it wouldn't work. Preferably use ID's or instances as suggested before:
class Block { /* TODO */ }
class Page {
@observable _currentBlock = null; // or id
constructor({ blocks }) {
this._blocks = blocks;
}
@computed get blocks() {
return this._blocks.map(block => new Block(block));
}
@computed get currentBlock() {
return this.blocks.find(block => block === this._currentBlock);
}
@action setCurrentBlock(block) { /*TODO*/ }; // now it expects Block instance
}
// the same can be done in App for Pages, just convert the pages to instances when user is recieved from server
EDIT: or you can use some custom equality check in these computeds
Edited previous comment. Note that if I would do the conversion to block instances inside the constructor, I would lost the reference to original blocks array, therefore any changes of to that original array would not update the page.blocks accordingly - that may or may not be a problem.
Oh bulshit :) It will work, without ref...the thing is you can obtain only already observable objects, therefore you will pass these observable objects to setCurrentBlock therefore the cloning won't happen again...
Cloning occurs only when the object isn't observable yet...
Thanks for your reply.
It helped me to organize my store better.
But the issue with update components styles on the page still exists.
I did a check and now currentBlock is referenced to the blocks in currentPage. So I think the problem is hidden in way that I receive block.style properties in the page component.
I'm build the page component like this :
And in edit panel I reference direct to the style properly, like
block.style.color
Maybe this is the reason why edit panel is updated in real time, and page.block only after I click on another block