How to re-render part of the component upon state change? - react-native

I have a component the includes progress.circle from the library react-native-progress (see link: https://github.com/oblador/react-native-progress).
Initially I set the progress prop of the progress circle to a state variable that is set to 0 at idle state as shown in the code below:
<Progress.Circle
showsText={true}
progress={this.state.progress} // this.state.progress = 0
size={width * 0.1388888888888}
color={"#EF5D6C"}
/>
Number of user interactions take place to set the state variable (this.state.progress) to a certain percentage then the component re-render to show the progress animation as intended.
Problem is the component flashes for part of a second as result of the re-rendering process.
So is there a way to only re-render the progress circle and leave the rest of the component in its current rendering form.

You could try separating out the Progress.Circle element into a different file so its in its own renderer.
render() {
return (
<Progress.Circle
showsText={true}
progress={this.state.progress} // this.state.progress = 0
size={width * 0.1388888888888}
color={"#EF5D6C"}
/>
)
}
That way when you change the element in state only the progress.circle should re-render. Then you would import this into your main file and call it there:
render(){
....
<ProgressCircle />
....
}
Basically whatever is inside the render function will re-render. If the progress.circle element is on its own where the state is changed, only it should re-render as far as i know.
Hope this helps in some way!

Make the progress.circle view as a separate component, as suggested in the previous answer. Keep the progress value in global state, which can be accessed by this separate component. If you are using Redux, you can do this easily. Then pass that state.progress value in the progress prop of the Progress.Circle

Related

Change state from other component (without passing setState function)

I have a quite decent background in android but now I am starting digging into react native and I am really confused with the ways to change the state of a component through hooks and set state function.
To the point, I have my main screen and I have many small components which change visibility. This is done by letting the user change some filter settings within dialogs. So the suggested way to do that is by having a hook in my main screen with a list that holds the values for the visibility of each component. But since I change the visibility of the components from inside the modals, every time I want to show a modal I will have to pass in a different function(for example setComponentEnable or setComponentDisabled) to set the state for each component. So my main screen will be polluted from all these small functions. Also I should not forget to mention that my modals are consisted from many smaller components and I will have to pass as deep as it goes the proper function to match the user action.
So my question is, is there a way to do this thing without polluting my main with all these small functions and make it possible for the main screen to update every time the user change the filters within the modals?
I already read about context but the docs say:
Context is designed to share data that can be considered “global” for a tree of React components, such as the current authenticated user, theme, or preferred language.
So I dont think that this should be a great case for context use.
What I am trying to do now is create a hook with a list
const [isibility, setVisibility] = useState([]);
create visibility handler functions
const setVisibilityEnable = () => {
...
}
and pass it into my modal.
<MyModal
visibilityHandler={setVisibilityEnable}/>
Is there a way to manipulate the state without passing all these callbacks to the modals? Or maybe is there anyone that can suggest a better and clean solution to avoid end up having a really huge main screen?
you can include all the settings in one object and pass that object to all the components. Then each component will then modify that object accordingly.
const defaultVisibility = {
childComponentOne: true,
childComponentTwo: true,
};
const [visibilityObject, setVisibilityObject] = useState(defaultVisibility);
pass both the function and the object into your child components:
<ChildComponentOne visibilityObject={visibilityObject} setVisibilityObject={setVisibilityObject} />
Then in your child component, you set the visibility like so:
setVisibilityObject({...visibilityObject, childComponentOne: false});
Why you don't just pass a property to your modal and check if changed in oncomponentdidchange method?
componentDidUpdate(prevProps) {
if (this.props.yourPoperty!== prevProps.yourPoperty) {
//do your visibility stuff
}
}
Alternatively you can do it with redux when you connect your components to the store.

track UI elements states with one object, but the states are not reserved once leaving the screen and coming back

In my react-native project, I have three checkboxes, I need to track the state of those checkboxes so I use an object with key-value (value is boolean) to represent the states of all three checkboxes and use useState hook to manage them. Here is my code:
import React, { useState, useEffect } from 'react';
...
const MyScreen = ({ navigation }) => {
// initially, all checkboxes are checked
const initialCheckBoxState = {
0: true,
1: true,
2: true,
};
const [checkBoxesState, setCheckBoxesState] = useState(initialCheckBoxState);
useEffect(() => {
return () => {
console.log('Screen did unmount');
};
}, [checkBoxesState]);
return (
<View>
...
<SectionList
sections={options}
renderItem={({ index, item }) => (
<CheckBox
onPress={() => {
const checkBoxesStateCopy = { ...checkBoxesState };
checkBoxesStateCopy[index] = !checkBoxesStateCopy[index];
setCheckBoxesState(checkBoxesStateCopy);
}}
/>
)}
/>
...
</View>
);
};
I omitted code that is not the concern of my problem. As you can see, for each item I draw one CheckBox component.
In practice, there are always three items (i.e. three check boxes to show). At the beginning I declared initialCheckBoxState, each key-pair represents the state of the checkbox of each. In the onPress callback of Checkbox I toggle each check box state & update the checkBoxesState by hook method setCheckBoxesState as a whole.
Everything works fine at runtime, my screen is re-rendered when toggling checkbox state, UI shows the status of checkboxes correctly. But issue comes when I navigate back to the previous screen and navigate back to this screen, all checkboxes states are back to the initial states.
So, why the checkboxes states are not reserved?
P.S. previous screen and MyScreen are under the same stack navigator. User press a button of previous screen to navigate to MyScreen. From MyScreen user can go to previous screen by pressing the "headerLeft" button
First lets answer the question:
why the checkboxes states are not reserved?
This component is handling its state completely independent, the state is created & handled inside and no values are passed-in from outside. what does it mean? this component has its initial state value inside of itself, it doesn't use any prop or anything else to initialize the state. everytime this component gets created, state is again initialized with that value. so that's the reason you lose all changes done to checkboxes, because when you leave this screen(component) , it gets unmounted(we'll talk about this in next question) and because all values are just handled inside, every data (containing checkboxes state) will be lost.
So now lets talk about this:
is react-native supposed to reserve the state when come back to the screen?
short answer is No. Every component is destroyed when unmounted including their state and data.
Now lets answer why
screens are still on the stack in memory, not destroyed?
Usually developers use a package like react-navigation or RNRF(which is built on top of react-navigation) for react navigation, most of times we don't care about how they handle this navigation logic, we just use the interface the provided us. each of these packages may have their own way to handle navigation. providing full answer to determine why exactly the screen in still in memory needs full code review and sure lots of debugging but i guess there are 2 possibilities. first as i said maybe the package you are using keeps the unmounted screens in memory at least for a while for some reason. the 2nd is a common react community issue which is Unmounted component still in memory which you can check at: https://github.com/facebook/react/issues/16138
And at last lets answer the question:
how do i keep checkboxes state even with navigating back and losing component containing their state?
This doesn't have just one way to that but simple and short answer is move your state out of the that component, e.g move it out to the parent component or a global variable.
to make it more clear lets explain like this: imagine screen A is always mounted, then you go in B and there you can see some checkboxes and you can modify the states. if the state is handled completely inside B, if you navigate back from screen B to A you lose all changes because B is now unmounted. so what you should do it to put checkboxes states in A screen then pass the values down to B. and when modifying the values, you modify A state. so when B gets unmounted all changes are persistant because you have them in A.
other approached exists as well, you can create a global singleton object named globalState. then put values needed to share between multiple screens there. if you prefer redux or mobx you can use them. one of their usages is when you have some data that you need to share between mutiple screens, these data are independent from where you are at and will persist.
This explanation is from official react-navigation documentation:
Consider a stack navigator with screens A and B. After navigating to
A, its componentDidMount is called. When pushing B, its
componentDidMount is also called, but A remains mounted on the stack
and its componentWillUnmount is therefore not called.
When going back from B to A, componentWillUnmount of B is called, but
componentDidMount of A is not because A remained mounted the whole
time.
https://reactnavigation.org/docs/navigation-lifecycle/#example-scenario
Your MyScreen screen is equivalent to screen B from the example, which means you can expect your screen to stay mounted if you navigate forward, but not backwards.
Its simple, just add a keyExtractor to your SectionList component, which would uniquely identify each checkbox, so that react knows which one to re-render on update.
You'll want to use AsyncStorage to persist data to the device. State variables will be cleared any time the component unmounts.
AsyncStorage docs:
https://react-native-community.github.io/asaync-storage/
import AsyncStorage from '#react-native-community/async-storage';
//You can only store string values so convert objects to strings:
const storeData = async (value) => {
try {
const jsonValue = JSON.stringify(value)
await AsyncStorage.setItem('#storage_Key', jsonValue)
} catch (e) {
// saving error
}
}
const getData = async () => {
try {
const jsonValue = await AsyncStorage.getItem('#storage_Key')
return jsonValue != null ? JSON.parse(jsonValue) : null;
} catch(e) {
// error reading value
}
}
UPDATE -
State is not being persisted due to the nature of React Component lifecycles. Specifically, when you navigate away from a screen the lifecycle method componentWillUnmount is called.
Here's an excerpt from the docs:
componentWillUnmount() is invoked immediately before a component is unmounted and destroyed. Perform any necessary cleanup in this method, such as invalidating timers, canceling network requests, or cleaning up any subscriptions that were created in componentDidMount().
...Once a component instance is unmounted, it will never be mounted again.
This means any values stored in state will be destroyed as well and upon navigating back to the screen ComponentDidMount will be called which is where you may want to assign persisted values back to state.
Two possible approaches aside from AsyncStorage that may work for some use cases to persist data across screens is using Context or a singleton.

What is the best way to initialize a state in ReactNative with custom values on a Modal?

So, I have a specific problem where i need to initialize a state with custom values that comes from the parent component props.theValue. But the problem is that the props.theValue isn't ready when we set the state. So this line should never work:
const [grams, setGrams] = useState(props.theValue)
The scenario:
We have a list of food on the Parent Screen, when the users Clicks on it, it opens a modal (Like a PopUp) with some options to choose before the selected item gets manipulated
What I tried?
Set initial flag useState(-1) to indicate it should set the value from props but again, the props isn't ready in this state
Save the state on the parent and pass the state with the set Function to the modal. But still it doesn't seems to be right
useEffect but whenever i call setGrams it get a Loop Rendering error, also the props.theValue is still undefined at this point
Example:
In this case, the both TextInput must have a initial value that comes
from the Parent Component and must be manipulated.
You could use Redux for manipulating your data between Components or you can try something like:
set a Global variable:
let previousObj = null
and then set a timeout to update your data only once, even if you set Modal invisible and Visible again
setTimeout(() => {
if(food.grams != undefined && previousObj != food){
previousObj = food
setGrams(""+food.grams)
}
}, 100)
You can either handle it in the child with default value or don't render the component in parent until the value is available:
{theValue && <Component theValue={theValue} />}
You can use useEffect to update the default value as well:
const [grams, setGrams] = useState(0)
useEffect(() => {
if (theValue) {
setGrams(theValue);
}
}, [theValue])

React Functional component is not rendering on props change

I am using react native for development and i am using functional component for development. but i am facing problem in it. As i am getting callback from function and saving it in a state and i want to that component will render again. but component is not rendering again. Please check and provide me detail in
callBackfn = (callback) => {
this.setState({ infomationtosend: callback });
}
this is working fine as if i will print it on console get state correct output. but as it again goes in the ComponentTreat it doesn't render component again. i also tried useEffect but not working. can anyone provide me solution.
<Swiper
from={0}
>
<ComponentTreat
detailsend={this.state.infomationtosend}
appName={"sentText"}
CallBack={(callback) => this.callBackfn(callback)}
/>
</Swiper>
problem in swiper duw to swiper component is not refreshing.
Functional Component state change works like this:
// Inside parent component
const [info, setInfo] = useState(initalValue);
Then to use it in a callback we use closures to access it instead of using this keyword.
function callback(newValue) {
setInfo(newValue);
}
And pass it accordingly to the child props.
Also, checkout useCallback() if you use a lot of callbacks.

react native component inside component

I have component that includes a textInput and another component with a slider that also has a textInput. Since both textInputs are exactly the same I am importing the component with the textInput into the component with the slider. When I focus on the textInput I want to make the slider disappear (focus: true -> hide slider , focus: false -> show slider)
Since the textInput is on a child component I'm using a callback to get the focus state of the child and based on this update the focus state on the parent.
The issue is that since I'm updating the state of the parent the whole thing gets rerendered and this also rerenders the child which returns the state again which makes the parent rerender again and makes the child rerender again which makes the child return the state and rerender the parent again and so on.
I did a console.log on both components and it seems this happens around 5 times until it stops. Sometimes with the slider showing even though focus is true other times the slider is hidden.
My question will be how do I make this run only once or at least how do I make the slider always hidden when focus = true (I have merge everything in one component and that solves the issue but I have another view that only needs the textInput so it will be great if I can split this in two components)
I think you're trying to change the state of the parent component using a lifecycle method like componentDidUpdate and that causes the rendering loop. Instead, use a function as a prop to set the new state, then use shouldComponentUpdate method like this:
In the child component:
func(param) {
this.props.setParam(param)
}
And in the parent component:
setParam(param) {
this.setState(
// set the param here
);
}
shouldComponentUpdate(nextProps, nextState) {
// do something with nextState
}
render() {
// do something
return <Child setParam={this.setParam} ... >;
}
That should work.