Mobx offers observer package which is only compatible with react, it makes it known to the store that data is being used and will rerun component on change. How do you replicate mobx observer support?
Mobx observers can be used like this:
#observable
name = 'bob'
function render( n ){ return (`<div>Hello ${n}</div>`) }
autorun( () =>{
document.getEelementById('baseOfApp').innerHTML = render( name )
})
As name changes the function passed to autorun will be re-run.
In this code react and mobx-react are not being used.
Mobx is not only for React is universal State management. That is even more technical solution for solving common problem in JavaScript apps.
You do not even need to use Provider component from react-mobx lib if you need propagate updated model into DOM.
Related
I need to re-render this state manually.
const [person] = React.useState(new Person());
I have methods inside the Person class to update it (e.g. person.setName('Tom')).
When I update person using a method from itself, it does not trigger a re-render on the person state.
const carouselData = React.useMemo(() => {
// Doesn't re-render when the fields on the person class update
}, [person]);
Is there a way to force this state to re-render without using a setState function?
Ideally, is there a way to call that re-render from inside the Person class itself?
Or is this totally misusing the useState functionality? Would there be a better React hook to connect this to?
Thanks!
Person could be a prop or in context instead. First create your instance outside of your component.
const person = new Person([]);
Then pass person as a prop.
function App({person}) {
const [personName, setPersonName] = useState(person.personName);
function handleNameChange(txt) {
person.addTodo(txt);
setPersonName(person.personName);
}
function handleSubmit(txt) {
handleNameChange(txt);
}
return (...)
}
you are indeed miss using it, react relies heavy on functional programming. you should be doing something like:
const [person, setPerson] = useState(new Person());
...
setPerson(setPersonName(person, 'Tom'));
that's just a silly example but you get the gist. react will only re-render if state is changed, it won't monitor if the state object is mutating or anything like angularjs used to do
I have a React table component that gets its data via a prop called TableStore. This prop is a high-level abstraction for getting row data:
interface TableStore<RowType> {
getRowIds: () => Array<RowId>;
getRow: (rowId: RowId) => RowType | undefined;
}
interface MaterialTableProps<RowType> {
tableStore: TableStore<RowType>;
}
function MaterialTable<RowType>(props: MaterialTableProps<RowType>) {
...
}
As you can see MaterialTable is not a MobX observer. It is part of a component library that is not dependent on MobX.
When I use this component in my app, I supply it a MobX-based TableStore. I would like the table component to re-render whenever the MobX-based store changes:
<MaterialTable tableStore={orderStore} />
However that does not happen because the table component is not a MobX observer. Is there any way to force the table component to re-render? For example, I am able to force a re-render by dereferencing the store in the parent component (using a simple console.log()). But this feels like a hack. Is there a better way?
Answering my own question....
I looked at several options but all of them were kludgy. I finally decided to rework the props of the table component to pass in an array instead of an abstract TableStore interface (which the table component can't react to). This allowed me to refrain from adding MobX as a dependency to the table component library while still leverage MobX in the parent component. In summary, the parent component now watches the MobX store, reacts to changes by creating a new array and passing it to the table component.
Here's the new interface for the table component:
export interface MaterialTableProps<T extends Entity> extends TableProps {
entityList: Array<T>;
}
export function MaterialTable<T extends Entity>(props: MaterialTableProps<T>) {
...
}
I have read this answer, but I want to change the structur of code a little bit,
actually I want to set parent state from child component but i don't want to add any function in parent component
actually the expected result looks like :
class Parent extends React.Component {
constructor(props) {
super(props)
this.state={modalVisible:false}
}
render() {
return (
<Child modalVisible={this.state.modalVisible} />
<Button onClick={()=>this.setState({modalVisible:true})/>
)
}
}
class Child extends React.Component {
handler(e) {
//handle parent moadlVisible state to false again
}
render() {
return
<Modal
modalVisible = {this.props.modalVisible}>
<Button title="Close Modal" onPress={()=>this.handler(e)}/>
</Modal>
}
}
so I want to make it easy to call the child component without add some function in parent to handle the child component itself, even for closing the modal of child component
Is there a way to achieve what I want?
If you don't want any connection between the 2 components at all, then you may have to use a global state store, such as redux.
Docs can be found here:
https://redux.js.org/introduction
Redux creates a global state instead of local state between all of the components. It does require a little configuration but once you have it fully integrated, you can handle your scenario. Also as your components grow, it'll be easier to keep track of state.
Why can't I just use events?
Quote in a question here..
react.js custom events for communicating with parent nodes
The React way would be to pass callbacks down to children explicitly
via props — . There's no support for custom events w/ bubbling in
React.
The reactive programming abstraction is orthogonal:
Programming interactive systems by means of the observer pattern is
hard and error-prone yet is still the implementation standard in many
production environments. We present an approach to gradually deprecate
observers in favor of reactive programming abstractions. Several
library layers help programmers to smoothly migrate existing code from
callbacks to a more declarative programming model.
A horrid way to do it in my eyes would possibly be to use Async Storage
to store the key value, but I'm not going to suggest that.
I am using react-native and react-redux to create a simple app. Thus, all of my props are passed in from mapStateToProps, coming from redux.
I should have to use mapDispachToProps to manipulate the props but that is not what is happening.
I have an event handler that allows me to directly manipulate the props. Why is this behavior occurring?
nameHandler(text) {
console.log(this.props.group.name); //output: Billy
this.props.group.name = 'illegal!';
console.log(this.props.group.name); // output: illegal!
}
I am binding 'this' to 'nameHandler' within the constructor, but that doesn't seem relevant.
I've mostly gotten around this, but shouldn't this not be possible?
EDIT AGAIN: Opened an issue with Reflux here: https://github.com/reflux/refluxjs/issues/544
EDIT: Reflux setState does not provide any callback for setState. They require you to use the component lifecycle methods to ensure the state is set prior to running any code. If you ever need to use the reflux setState outside of a component, where you do not have lifecycle methods, you will not be guaranteed the state is set. This is due to how Reflux does their setState. It loops all listening components and calls those components' setState methods. If Reflux were refactored to wait until all the listening components' setState calls complete then call a callback passed into its own setState method, that may work, but it would likely require a large rework of Reflux. I have started using a singleton class to manage some of these variables, as they are fully outside the component lifecycle.
Can you use setState with a callback in ReactNative or is that only in React? I'm using the below syntax and the first debugger is hit, but the second debugger and console log never get hit.
EDIT: After digging some more, it seems this does not occur when using setting the state directly, but only when running it through a reflux store and/or not using a component.
See snack here: https://snack.expo.io/S1dm3eFoM
debugger
this.setState(
params,
() => {
debugger
console.log("CALLIN IT BACK")
}
)
I'm the creator of Reflux's ES6 styled stores/component hookups. Hopefully I can shed some light on this for you.
Here's the important points:
1) Reflux sets its store state immediately upon setState calls.
Reflux's store state doesn't have the same problems as React and doesn't need React's workaround (callback). You are guaranteed that your change is immediately reflected in the store's state, that's why there is not a callback. The very next line of code will reflect the store's new state.
tl;dr, no workaround is required.
// in Reflux stores this works
this.setState({foo:'foo'});
console.log(this.state.foo === 'foo') // true
this.setState({foo:'bar'});
console.log(this.state.foo === 'bar') // true
2) Stores can never depend upon components!
The idea that the setState would give a callback about when the dependent components have all updated their state is a major violation of the single most fundamental of all flux principles: 1 way data flow.
If your store requires knowledge about whether or not components are doing something then you are already doing it wrong, and all the problems you are experiencing are XY problems of fundamentally not following flux in the first place. 1-way data flow is a main flux principle.
And that principle exists for good reason. Flux doesn't require 1:1 mapping of store state properties to component state properties. You can map anything to anything, or even just use the store's state for the building blocks of how you will run your own logic to create completely new state properties on the components. For example having loaded and transitioned as separate properties in store state, but mapping to a loadedAndTransitioned property in one component, and a notLoadedOrTransitioned in another component via your own custom logic. That's a hugely powerful part of flux. But your suggestion would pretty much destroy all that, since Reflux can't map people's custom logic.
1-way data flow must be maintained; Store's must operate the same independently of what components utilize them. Without this, the power of flux falls apart!
Store's listen to actions, components listen to stores, actions are called from wherever. All flux-based data flows from action -> store -> component only.
I've checked the library for the refluxjs and the problem and the workaround are as mentioned below.
Problem
The library provides with a new instance of the setState which is not exactly similar to ReactJS setState, which omits the callback as mentioned in their code below.
/dist/reflux.js
proto.setState = function (obj) {
// Object.assign(this.state, obj); // later turn this to Object.assign and remove loop once support is good enough
for (var key in obj) {
this.state[key] = obj[key];
}
// if there's an id (i.e. it's being tracked by the global state) then make sure to update the global state
if (this.id) {
Reflux.GlobalState[this.id] = this.state;
}
// trigger, because any component it's attached to is listening and will merge the store state into its own on a store trigger
this.trigger(obj);
};
Also as mentioned here in the docs
That store will store its state on a this.state property, and mutate its state via this.setState() in a way that is extremely similar to React classes themselves.
WorkAround
The library provides with the listener functions, which provide us with the callbacks of the setState obj of the ReactJS as mentioned in the below snippet.
/dist/reflux.js
componentDidMount: function() {
var me = this;
_.extend(me, ListenerMethods);
this.listenTo(listenable, function(v) {
me.setState(_.object([key],[v]));
});
},
You can use them in the following way
this.listenTo(action, callback)
Hope it clears the doubts
Edit:
Usage as per the docs
To listen inside of the store
constructor()
{
super();
this.state = {count: 0};
this.listenTo(increment, this.incrementItUp);
}
incrementItUp()
{
var newCount = this.state.count + 1;
this.setState({count: newCount});
}
To listen outside of the store anywhere
// listen directly to an action
myActions.actionName.listen(myCallbackFunc);
// listen to a child action
myActions.load.completed.listen(myCallbackFunc);
Here's the link to the snack with working callbacks based on Promises