Re-render React useState without updating the state - react-native

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

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().

useState() hook with initial value but its not reflected in the state variable

I have an object myObj which is a field of data object. I use useState hook to initiate a state variable with this object.
myObj is like {"name": "John", "age": 20, ...}.
import React, {useState} from 'react';
const myComponent = ({data}) => {
const[student, setStudent] = useState(data.myObj); // myObj is a object with value
console.log(`${JSON.stringfy(student)}`); // it prints undefined, why?
return (
// here I need to render the student information but it is undefined.
)
}
Do I mis-use the useState hook here? If so, how can I have the initial object value being reflected right after useState hook like showing in the console log above?
The Issue here is your state is dependent on the props. But you need to check once whether this is needed, it might be needed if your state is a transformed value of your prop .
But if you are not doing much with your props, then you can safely remove it and consume the props directly and use it for rendering the elements . Because re-render in react is triggered even when the props of your component changes.
This article has a good explanation on why state dependent on props is needed in most cases.State Dependent on Props challenges
As I can understand, your state of student is not getting refreshed. If so, you have to use useEffect hook.
See, how?
useEffect(() => {
console.log(`${JSON.stringfy(student)}`);
}, [student])
In your case you are using student outside return block, that's why console is not getting printed. Using useEffect you will get update once it gets stored successfully.
If console is not important for you then you can directly print student's value without using useEffect,
return (
<View>
<Text>{student}</Text>
</View>
)

Any way to make a React presentational component react to MobX store changes

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>) {
...
}

Can mobX tell me which elements in an observable array or object were changed?

I am using mobX with React and Three.js. The Three.js scene is rendered in a React component. I use the mobX store for information rendered both in normal React components as well as the 3D scene (this is not a graphics or React question though).
To update the ThreeJS scene when relevant changes happen in the mobX store, I have a custom reaction:
import { reaction } from 'mobx';
import { observer, inject } from 'mobx-react';
// ...
#inject('matchStore')
#observer
class MapViewport extends React.Component {
// Update the scene when the map state changes
reactToSceneUpdate = reaction(
() => return this.props.matchStore.territories,
() => this.updateScene()
);
// ...
That reaction is correctly updating the scene when matchStore.territories changes. matchStore. matchStore.territories is an array computed from an observable object:
class MatchStore {
#observable territoriesById = {};
#computed get territories() {
return Object.values(this.territoriesById);
}
// ...
The problem is that matchStore.territories is a very large array, and the updateScene function has to iterate through every member of the array to check if it needs graphics updates. I've noticed that this takes some time and has a noticeable lag.
Is there a way that mobX lets me get the specific member(s) of the array that triggered the change?
Alternatively, can I use the observable object in the reaction (matchStore.territoriesById) and somehow get the member(s) of the object that were changed?
My best idea so far is to set a flag on the updated members that's checked in the updateScene loop (and members not in need of updating are instantly skipped over), but I was hoping for a more implicit solution.
To get separate information about updates to individual array elements, define a Territory class with its own #observable properties, and create an instance of that class to be each element of the array. Set up a separate reaction for each element. That way, if its data changes you would make specific updates to the scene relating to that element.

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

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