React Native + Redux: How to subscribe to changes in state? - react-native

I have a component and I want to call a method checking the state whenever it changes. This is my component with a dummy method to demonstrate what I want to do (animate the view offscreen if onboarding.show === false):
export class Onboarding extends Component {
animateView() {
// i want to call this method when
// the state changes
// something like;
if (!this.props.onboarding.show) {
Animated.spring(...);
}
}
render() {
const { onboarding, finish } = this.props;
return (
<Animated.View>
...
</Animated.View>
);
}
}
...
export default connect(
state => {
return {
onboarding: state.onboarding,
};
},
dispatch => {
return {
};
}
)(Onboarding);
Is there a way to subscribe to the changes in state?
== UPDATE ==
as requested, here's what my slideOffScreen method does:
slideOffScreen() {
Animated.timing(this.state.offsetX, {
toValue: -Dimensions.get('window').width,
duration: 350,
easing: Easing.elastic(),
}).start();
}

The react-redux connect method wraps the component with a container component that is aware of the store's state changes. Whenever the state changes, connect re-renders the wrapped component (Onboarding in your case).
According to the redux docs:
Technically, a container component is just a React component that uses
store.subscribe() to read a part of the Redux state tree and supply
props to a presentational component it renders. You could write a
container component by hand, but we suggest instead generating
container components with the React Redux library's connect()
function, which provides many useful optimizations to prevent
unnecessary re-renders.
If your component doesn't re-rendered when the state changes, check if you're not mutating the state instead of replacing it. Redux checks if the state changed by shallowly comparing the old state, and the new state (comparing only the references, and not the values).
For example, to add an item to an array, you can't use array.push(item) because that won't create a new array, just mutate the existing one. Instead you'll have to use something like array.concat(item), which does.
To update objects, you can see in the redux docs under handling actios example, you can see that to create a new state:
We don't mutate the state. We create a copy with Object.assign().
Object.assign(state, { visibilityFilter: action.filter }) is also
wrong: it will mutate the first argument. You must supply an empty
object as the first parameter. You can also enable the object spread
operator proposal to write { ...state, ...newState } instead.

Looks like this works:
componentWillReceiveProps(props) {
if (!props.onboarding.show) {
this.slideOffScreen();
}
}
not sure if there's a way to do it through the redux API

Related

Reload a React Native class component

I have a class component directions in my project. I navigate to another component from it using this.props.navigation.navigate(). Now the problem is that I want to navigate back to the same directions component but with passing new values, ie I want it to reload from scratch, defining state variables once again. How can I do it?
Using navigation.navigate() simply takes me back to the previous state the screen has been.
this.props.navigation.navigate('direction',{
riderLocation:this.state.rideInfo.location,
ride_id:this.state.ride_id,
});
And this is the componentDidMount of directions.
componentDidMount(){
alert('componentDidMount');
const {navigation,route}=this.props;
this.state.riderLocation = navigation.getParam('riderLocation');
this.state.ride_id= navigation.getParam('ride_id');
}
In the "directions" component, use "componentDidMount" method.
Inside "componentDidMount" method, call a function which updates the state value as desired.
Once you are redirected back to the "directions" component, then "componentDidMount" will run and the state will be updated.
====
Edit:
Try using componentDidUpdate() method in "directions" component.
componentDidUpdate(prevProps, prevState) {
if (prevProps.navigation.getParam('ride_id') !== this.props.navigation.getParam('ride_id')) {
const {
navigation,
route
} = this.props;
this.setState({
riderLocation: navigation.getParam('riderLocation'),
ride_id: navigation.getParam('ride_id')
})
}
}
Also instead of "this.state.riderLocation" and "this.state.ride_id" use this.setState in componentDidMount(), just like I have written in componentDidUpdate().

Re-render React useState without updating the state

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

React Hooks and useEffect – best practices and server issues

I am using React Native with functional components. componentDidMount() etc. are not available in functional components, instead I use Hooks. But Hooks don't act like lifecycle methods. I am wondering what the best practices are.
Assumed that we have a function like this one:
const ABCScreen = () => {
const [someHook, setSomeHook] = useState<any>()
useEffect(() => {
// some code inside this function which is called on every component update
}, [])
server.asyncCall().then(data => {
setSomeHook(data)
})
return (<View>
{someHook ? (<Text> `someHook` was assigned </Text>) : (<Text> `someHook` was not assigned, display some ActivityIndicator instead</Text>)}
</View>)
}
Where to place server.asyncCall()? Inside or outside of useEffect?
I think you have a misunderstanding here. The convention is that all the fetching data is going to be placed inside the componentDidMount lifecycle method. React useEffect hook can replace this easily by placing an empty array of dependencies, which means you can place that call inside the useEffect you already have.
Unlike you mention in your code comment, this hook won't be triggered on each component update. It will be only be triggered once the component is being mounted. So, you should be able to do it as follows:
const ABCScreen = () => {
const [someHook, setSomeHook] = useState<any>()
useEffect(() => {
server.asyncCall().then(setSomeHook)
}, [])//only triggered when component is mounted.
In the future, you might want to check the rules of the hooks.

Why I can't read a state in react native?

I have the following code:
export default class Testing extends Component {
state = ({
data: []
});
componentDidMount() {
this.setState({
data: this.props.values
});
console.log(this.state.posts); //prints empty but if I do
console.log(this.props.values); //prints the array correctly
}
Where is the error since I can print the props not the state?
Thanks
You're not storing anything in this.state.posts. Your initial state only contains data.
Also when you construct your initial state you should do it like this:
state = {
data: []
}
You do not need the ( ) around it.
If you are wanting to print a value from state as soon as you have stored it you must use the callback functionality of state. This is due to the fact that setState is asynchronous and takes time to set the value. Currently you are trying to read the value before it has been set, use the callback functionality like below.
this.setState({
data: this.props.values
}, () => console.log(this.state.data));
Here are some great articles on setState.
https://medium.learnreact.com/setstate-is-asynchronous-52ead919a3f0
https://medium.learnreact.com/setstate-takes-a-callback-1f71ad5d2296
https://medium.learnreact.com/setstate-takes-a-function-56eb940f84b6
you don't need the ( ) when you set the initial state because it is an object.
export default class Testing extends Component {
state = { //remove (
data: []
}; //remove )
Also worth noting, setState is an async function. You will not be able to getState directly after setState.
In order to get the state right away, you would provide a callback to setState() https://reactjs.org/docs/react-component.html#setstate

Subscribe to single property change in store in Redux

In Redux I can easily subscribe to store changes with
store.subscribe(() => my handler goes here)
But what if my store is full of different objects and in a particular place in my app I want to subscribe to changes made only in a specific object in the store?
There is no way to subscribe to part of the store when using subscribe directly, but as the creator of Redux says himself - don't use subscribe directly! For the data flow of a Redux app to really work, you will want one component that wraps your entire app. This component will subscribe to your store. The rest of your components will be children to this wrapper component and will only get the parts of the state that they need.
If you are using Redux with React then there is good news - the official react-redux package takes care of this for you! It provides that wrapper component, called a <Provider />. You will then have at least one "smart component" that listens to state changes passed down by the Provider from the store. You can specify which parts of the state it should listen to, and those pieces of the state will be passed down as props to that component (and then of course, it can pass those down to its own children). You can specify that by using the connect() function on your "smart" component and using the mapStateToPropsfunction as a first parameter. To recap:
Wrap root component with Provider component that subscribes to store changes
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root')
)
Now any child of <App /> that is wrapped with connect() will be a "smart" component. You can pass in mapStateToProps to pick certain parts of the state and give it those as props.
const mapStateToProps = (state) => {
return {
somethingFromStore: state.somethingFromStore
}
}
class ChildOfApp extends Component {
render() {
return <div>{this.props.somethingFromStore}</div>
}
}
//wrap App in connect and pass in mapStateToProps
export default connect(mapStateToProps)(ChildOfApp)
Obviously <App /> can have many children and you can pick and choose which parts of the state the mapStateToProps should listen to for each of its children. I'd suggest reading the docs on usage with React to get a better understanding of this flow.
Redux only offers a single generic way to know when the store has updated: the subscribe method. Callbacks to subscribe do not get any info on what might have changed, as the subscribe API is deliberately low-level, and simply runs each callback with no arguments. All you know is that the store has updated in some way.
Because of that, someone has to write specific logic to compare old state vs new state, and see if anything has changed. You could handle this by using React-Redux, specifying a mapStateToProps function for your component, implementing componentWillReceiveProps in your component, and checking to see if specific props from the store have changed.
There are also a couple addon libraries that try to handle this case: https://github.com/ashaffer/redux-subscribe and https://github.com/jprichardson/redux-watch . Both basically let you specify a specific portion of the state to look at, using different approaches.
In addition to what Andy Noelker said, mapStateToProps not only passes part of the state properly down your component tree, it also subscribes to changes made directly in these subscribed portions of the state.
It is true that every mapStateToProp function you bind to the store gets called each time any part of the state is changed, but the result of the call gets shallow compared to the previous call - if top level keys you subscribed onto did not change (the reference stays the same). Then mapStateToProps would not call re-render. So if you want the concept to work, you have to keep mapStateToProps simple, no merging, type changing or anything, they should simply pass down parts of the state.
If you want to reduce the data from the state when subscribing, for example you had list data in the state and you want to convert it to object with ids as keys, or you want to join multiple states into data structures, you should combine mapStateToProps with createSelector from reselect library, by doing all these modifications inside selector. Selectors are pure functions that reduce and cache state chunks passed in as input and if input did not change - they return exactly the same reference they did on the last call - without performing the reduction.
Created a hack to help understand the subscribers can be differentiated based on store data, with multiple store capability.
//import { createStore } from 'redux';
let createStore = require('redux').createStore;
let combineReducers = require('redux').combineReducers;
/**
* This is a reducer, a pure function with (state, action) => state signature.
* It describes how an action transforms the state into the next state.
*
* The shape of the state is up to you: it can be a primitive, an array, an object,
* or even an Immutable.js data structure. The only important part is that you should
* not mutate the state object, but return a new object if the state changes.
*
* In this example, we use a `switch` statement and strings, but you can use a helper that
* follows a different convention (such as function maps) if it makes sense for your
* project.
*/
function counter(state = 0, action) {
switch (action.type) {
case 'INCREMENT':
return state + 1
case 'DECREMENT':
return state - 1
default:
return state
}
}
function messanger(state = 'Mr, khazi', action) {
switch(action.type) {
case 'WELCOME':
return 'Hello, Mr Khazi';
case 'BYE':
return 'Bye, Mr Khazi';
case 'INCREMENT':
return 'Incremented khazi';
default:
return state;
}
};
function latestAction(state = null, action) {
switch(action.type) {
case 'WELCOME':
return '$messanger';
case 'BYE':
return '$messanger';
case 'INCREMENT':
return '$messanger, $counter';
case 'DECREMENT':
return '$counter';
default:
return state;
}
};
let reducers = {
counts: counter,
message: messanger,
action: latestAction
};
let store = createStore(
combineReducers(reducers, latestAction)
);
// Create a Redux store holding the state of your app.
// Its API is { subscribe, dispatch, getState }.
//let store = createStore(counter)
// You can use subscribe() to update the UI in response to state changes.
// Normally you'd use a view binding library (e.g. React Redux) rather than subscribe() directly.
// However it can also be handy to persist the current state in the localStorage.
store.subscribe(() => {
if(store.getState().action.indexOf('messanger') !== -1) {
console.log('subscribed for counter actions', store.getState());
}
});
store.subscribe(() => {
if (store.getState().action.indexOf('counter') !== -1) {
console.log('subscribed for messanger actions', store.getState());
}
});
// The only way to mutate the internal state is to dispatch an action.
// The actions can be serialized, logged or stored and later replayed.
console.log('----------------Action with both subscriber-------------');
store.dispatch({ type: 'INCREMENT' });
console.log('---------------Action with counter subscriber-----------');
store.dispatch({ type: 'DECREMENT' });
console.log('---------------Action with messenger subscriber---------');
store.dispatch({ type: 'WELCOME' });
/*
every reducer will execute on each action.
*/