autorun to current navigation params - react-native

Im trying to listen to changes in current navigation params.
I tried the following code:
#observer
export default class TestScreen extends Component<PropsType> {
#computed get currParams(): ?Object {
return this.props.navigation.state.params;
}
constructor(props: PropsType) {
super(props);
setTimeout(() => {
props.navigation.setParams({ a: 'b' });
}, 1500);
this.aa = autorun(() => {
console.log('currParams', this.currParams);
console.log('a', this.currParams && this.currParams.a);
});
}
render(): React$Element<any> {
console.log('R', this.props.navigation.state.params);
return (
<View />
);
}
}
Prints:
currParams undefined
a undefined
R undefined
R undefined
R {a: "b"}
R {a: "b"}
meaning that the screen rendered with the new values but the autorun didn't saw that the params changed.
If I change the console.log in the render method to print this.currParams it prints undefined all the time.
How can I observe current navigation params?
Thank u.

That is probably because the navigation state is not observable.
#computed properties are only triggered to re-evaluate after one of the used #observable properties has been triggered.
Your render state does get triggered on changing Props and thus the render logs but not the mobx autorun (the render function is a react thing and triggered here on prop changed, the autorun only triggers on changes in mobx observable state).
Thus make sure the this.props.navigation.state.params is #observable. best make it observable at its root.
So in short: You can't use #mobx.computed on React.State. React state is not observed and thus wont trigger any changes in your Mobx State!
See here an example on how to use Mobx State directly in a react component: https://alexhisen.gitbooks.io/mobx-recipes/content/use-observables-instead-of-state-in-react-components.html

Related

Re-render component everytime screen is opened react native

I'm kinda new to React Native. I'm using the getFamily() on my screen MyFamily but when I go to another screen there change the value of the Family and come back to my MyFamily screen then I don't see the changes.
I tried doing it with the useEffect but still nothing happens, also the log doesn't happen. How can I solve this?
export default function MyFamily({ navigation, props, person, inheritors }) {
console.log(getFamily());
let [family, setFamily] = useState(getFamily());
useEffect(() => {
console.log(getFamily());
setFamily(getFamily());
}, [getFamily]);
In the screen where I set the Family again I do this:
And I know that's correct because the Json that is shown shows the updated value.
import { setFamily } from '../../utilities/family';
setFamily(responseJson.family);
This is the way family is formulated:
let family = '';
export default family;
export function getFamily() {
return family;
}
export function setFamily(f) {
family = f;
}
React doesn't actually know that the value returned from the getFamily function changes each render. In the useState function, it's only used in the initial state, and the useEffect function never gets re-run because the getFamily function itself doesn't ever change and re-trigger the useEffect. You have to change the getFamily() function to use a state that's stored in a parent component and pass it into the MyFamily component as a prop.
e.g.
// the parent component that renders the MyFamily screen
function Router() {
const [family, setFamily] = useState('')
return (
<Navigator>
<Screen component={<MyFamily family={family} setFamily={setFamily} />
<Screen component={<OtherComponent family={family} setFamily={setFamily} />
</Navigator>
}
)
}
And then from MyFamily:
function MyFamily({ family }) {
console.log(family); // this should be updated
}
and from OtherComponent:
function OtherComponent({ setFamily }) {
return (<Button onClick={() => setFamily('newFamily')>Change family</Button>)
}

why chatMsgStore.addChatMsg(bdmsg) does not effect the store?

store.js
import {useLocalObservable} from "mobx-react-lite";
function chatStore() {
return {
chatmsg: [],
setChatMsg(arr) {
this.chatmsg = arr
},
addChatMsg(msg) {
this.chatmsg.push(msg)
}
}
}
export const useChatStore = () => useLocalObservable(chatStore)
app.js
const App = () => {
const chatMsgStore = useChatStore()
const AppFunctions = {chatMsgStore}
useEffect(() => {
socket.on(activechat.chatid, (bdmsg) => {
chatMsgStore.addChatMsg(bdmsg)
})
return () => {
socket.off(activechat.chatid)
}
}, [activechat, chatMsgStore.chatmsg])
return (
<>
<AppContext.Provider value={AppFunctions}>
.....................
</AppContext.Provider>
</>
)
}
export default App;
fetch.js
async function getChatMessages(url, body, userStore, chatMsgStore) {
........
chatMsgStore.setChatMsg(firstResData)
........
on app load i add a socket listener which deps are activechat and chatMsgStore.
this listener is dynamic and must be changed when deps change.
the only purpose of this listener is to add a msg to the store and re-render the observer component
deps :
activechat - non store state
chatMsgStore.chatmsg - store state
why chatMsgStore.addChatMsg(bdmsg) does not effect the store? so deeply nested components inside App.js is not re-rendering.
otherwise i have a function getChatMessages which i import from custom hook deep inside App.js which sets the messages. this func is not a child of App.js and it is not wrapped with observer chatMsgStore.setChatMsg(firstResData) works! i can set the message so the observer component will re-render
how to make this code in useeffect above work?
Your App component is not wrapped with observer HOC so it won't react to observable values changes.
Wrap it like that:
const App = observer(() => {
// ...
})
or when exporting:
export default observer(App)
More info in the docs
you should use autorun from mobx in order to set correctly the reactivity in useEffect, here is a link to the doc that explains why and how use it.
But I think that you should not put chatMsgStore.chatmsg inside the deps array because you're not using it inside the useEffect.
If you can provide a working example maybe we can help you further.

React Native, How to use Redux store after dispatching in UseEffect?

I have a react-native functional component that uses UseEffect to dispatch an action to the Redux store to update a field. After the field is updated, I would like the component to use the data to decide whether to show the data or navigate away.
const myScreen = props => {
const fieldFromStore = useSelector(state=> state.mystore.myfield)
useEffect(
()=>{
dispatch(actionThatWillUpdateMyField)
if (fieldFromStore == Something){
props.navigation.navigate('NextPage')
} //else just render the rest of the screen
}, [])
return (
<View>
<Text> {fieldfromStore}</Text>
<View>)
The problem is the fieldFromStore in useEffect will always be null under effect as during that render of useEffect the store has not been updated yet.
Am I violating some sort of best practice here? How can I dispatch an action to update Store and then use that data to then determine how the page is rendered?
Thank you very much for the help.
Use a 2nd useEffect() block to handle the field change. The 2nd block should have fieldFromStore as a dependancy, so it will react to changes in the field:
const myScreen = props => {
const fieldFromStore = useSelector(state => state.mystore.myfield)
useEffect(() => {
dispatch(actionThatWillUpdateMyField)
}, [])
useEffect(() => {
if (fieldFromStore === Something) {
props.navigation.navigate('NextPage')
} //else just render the rest of the screen
}, [fieldFromStore, props.navigation])
// ...
}
You can use the dependency array of useEffect to control which selectors will cause your function to run again. So, instead of the empty array at the tail of your useEffect, use [ fieldFromStore ]. Full function below for clarity's sake
useEffect(()=> {
dispatch(actionThatWillUpdateMyField)
if (fieldFromStore == Something) {
props.navigation.navigate('NextPage')
} //else just render the rest of the screen
}, [ fieldFromStore ]);
The 'best practices' this might violate is that it will dispatch your action again when the selector changes. One way around this would be to dispatch the action before you navigate to the component you're on, and then a re-render here would be cleaner.

How to navigate in mobx store using react navigation?

I can use this.props.navigation from screen component to navigate. How should I do the similar in mobx store file? Or should I perform navigation in store?
I read the Navigating without the navigation prop article, but it seems only works for screen components, right?
Someone says use global variable to store a this.props.navigation reference and use it anywhere, but I don't like the idea...
Yes either:
forward the navigation class to the store when calling the method:
// add nivagation parameter to store fucntion:
this.props.store.handleSomething(data, this.props.navigation);
Or you can singleton the navigator (warning only works for one ofc):
return <Navigator ref={(nav) => this.props.store.navigator = nav} />;
after this is rendered it will set the navigator property in the store.
But I would suggest to store all your routing state also in a routing store like this: https://github.com/alisd23/mobx-react-router.
This way you always have easy access to the navigation state and you can be sure everything properly re-renders. (when in render function in components also depends on navigation changes!)
You can keep all your states including navigation state in mobx store.
For example:
// sourced and modified from https://github.com/react-community/react-navigation/issues/34#issuecomment-281651328
class NavigationStore {
#observable headerTitle = "Index"
#observable.ref navigationState = {
index: 0,
routes: [
{ key: "Index", routeName: "Index" },
],
}
// NOTE: the second param, is to avoid stacking and reset the nav state
#action dispatch = (action, stackNavState = true) => {
const previousNavState = stackNavState ? this.navigationState : null;
return this.navigationState = AppNavigator
.router
.getStateForAction(action, previousNavState);
}
}
// NOTE: the top level component must be a reactive component
#observer
class App extends React.Component {
constructor(props, context) {
super(props, context)
// initialize the navigation store
this.store = new NavigationStore()
}
render() {
// patch over the navigation property with the new dispatch and mobx observed state
return (
<AppNavigator navigation={addNavigationHelpers({
dispatch: this.store.dispatch,
state: this.store.navigationState,
addListener: () => { /* left blank */ }
})}/>
)
}
};
Then you can directly call the dispatch action of the store to navigate to a new screen.
Send this one this.props.navigation as a parameter to the store. Then use as you use on the component side.
LoginStore.login(this.props.navigation)
in the LoginStore
#action login = (navigation) => { navigation.navigate('Page');}

Realm & React Native - Best practice to implement auto-updates?

What are the best practices/patterns make realm a reactive datasource in a react native app? Especially for presentational and container components pattern?
Here is an example which I'd like to make reactive: Realm with React Native
The docs on auto-updates/change-events are a bit thin and the official example does not make use of this feature (to my knowledge).
You can make your example reactive by subscribing to events and updating the ui when you receive a change event. Right now events are only sent when write transactions are committed, but finer grained change events will be added in the future. For now you could add the following constructor to update the ui on changes:
constructor(props) {
super(props);
this.realm = new Realm({schema:[dogSchema]})
this.realm.addListener('change', () => {
this.forceUpdate()
});
}
You need to hold onto a Realm instance to keep the notifications alive, and you can use this Realm instance throughout the rest of the component.
Instead of calling forceUpdate, you could instead set the component's state or props within the event listener to trigger the refresh, like so:
constructor(props) {
super(props);
this.realm = new Realm({schema:[dogSchema]})
this.state = {...}; // Initial state of component.
this.realm.addListener('change', () => {
this.setState({...}); // Update state instead of using this.forceUpdate()
});
}
I think #Ari gave me a good answer for redux folks as i was also struggling. I'm not sure if it's immutable enough but it works!
I'm simpliy dispatching getVehicles action inside addListener and it just works!
Below is UI component whose constructor function makes the magic!
//- importing my realm schema
import realm from '../../db/models';
//- Importing my action
import { getVehicles } from './../../actions/vehicle';
#connect((store) => {
return {
vehicle: store.vehicle.vehicles
}
})
export default class Devices extends Component {
constructor(props) {
super(props);
realm.addListener('change', () => {
props.dispatch(getVehicles());
});
}
}
Below is db/models file used up there in the constructor.
import Realm from 'realm';
class VehicleSchema {};
VehicleSchema = {
name: 'vehicleInfo',
properties: {
vehicleName: 'string',
vehicleNumber: 'string',
vehiclePassword: 'string',
vehiclePasswordTrigger: 'bool',
vehicleType: 'string',
vehiclePicture: { type: 'data', optional: true }
}
};
export default new Realm({schema: [VehicleSchema]});
Below is the actions/vehicle file, which gets dispatched in the constructor above.
import { queryVehicle } from './../db/queryVehicle';
export function getVehicles() {
const vehicles = queryVehicle();
return function(dispatch) {
dispatch({type: "GOT_VEHICLES", payload: vehicles});
}
}
Below is my queryVehicle function that does the querying called in action file above.
import vehicleModel from './models';
const queryVehicle = (queryInfo="vehicleInfo", filter='') => {
const objects = vehicleModel.objects(queryInfo);
if(filter.length === 0) return objects;
let results = objects.filtered(filter);
return results;
};
export { queryVehicle };
disclaimer I don't know if this code looks immutable enough, or following good redux practice cause i'm just starting out with redux so give me some comments advising if i'm doing something wrong.
I'll also guess reducer implementation wouldn't matter much in this here.
Recently ran into an issue with Realm ListView auto-updating. When the ListView rows have varied heights, you can get overlaps on rows in the UI. The below was the only way I could get the ListView to re-render without causing UI overlaps. It seems a bit "dirty" to me, so if there is a better way, I welcome the input. But this is working perfectly so far; incase anyone else runs into this issue.
Basically it just wipes the dataSource, then inserts it again using the setState callback when there are insertions or deletions, but modifications simply roll through and auto-update.
let feed = this.props.store.feed;
feed.addListener((name, changes) => {
if (changes.insertions.length || changes.deletions.length) {
this.setState({dataSource: this.ds.cloneWithRows([])},
() => this.setState({dataSource: this.ds.cloneWithRows(feed)})
);
} else {
this.setState({dataSource: this.ds.cloneWithRows(feed)});
}
});