Show updated variable on another screen React Native - javascript

I have my ScreenOne.js file where I have a variable called points with a starting value of 0. I also have some functions in the file that add a value of 10 to the points.
The updated points variable is shown on the ScreenOne and it is updating well. I also want to display the updated points variable in a screen called ScreenTwo.js but that seems to be a problem.
I have already tried to store the updated value in a local storage but it returns null. In this case I try to export the updated variable but it return the variable that has the starting value instead of the updated value.
What could be the best way to transfer and display the updated variable in ScreenTwo?
ScreenOne:
const ScreenOne = ({ navigation }) => {
let valuePoints = 0
module.exports.valuePoints = valuePoints;
if (userInput1.search('No') < 0) {
console.log('Wrong answer!')
} else {
valuePoints += 10
}
if (userInput2.search('Yes') < 0) {
console.log('Wrong answer!')
} else {
valuePoints += 10
}
return (
<ScreenContainer>
<ScrollView style={{ flex: 1, width: '100%' }}>
<PointsAmount>
{valuePoints} //Here it displays 20
</PointsAmount>
</ScrollView>
</ScreenContainer>
)
}
export default ScreenOne;
ScreenTwo:
const ScreenTwo = ({ }) => {
const {valuePoints} = require('../screens/ScreenOne');
return (
<PointsContainer>
<ScrollView style={{flex:1, width: '100%'}}>
<TouchableOpacity onPress={() => navigation.navigate('MyTabs1')} >
<ScoreCard header='Points gained' points={valuePoints} goal={100} /> //Here it should display 20 but instead it displays 0
</TouchableOpacity>
</ScrollView>
</PointsContainer>
)
}
export default ScreenTwo;

You should have a look at the docs for the context in react.
You need to create a context that will be wrapping your App.
const [points, setPoints] = useState(0);
<Context.Provider value={{points, setPoints}}>
Your app
</Context.Provider>
Then in your ScreenOne and ScreenTwo you can get and set the value with:
const {
points, setPoints
} = useContext(Context);

Related

“react-infinite-scroll-component” not firing next function

I'm using the react-infinite-scroll-component to implement the infinite scroll component. The configuration of the component is:
<div id="scrollableDiv" style={{ height: 300, overflow: "auto" }}>
<InfiniteScroll
dataLength={texts.length}
next={getText}
hasMore={true}
loader={<h5>Loading...</h5>}
endMessage={<p style={{textAlign:'center'}}><b>Yay! You've seen it all!</b></p>}
scrollableTarget="scrollableDiv"
>
{<Texts texts = {texts}/>}
</InfiniteScroll>
</div>
where texts is simply a state array of some text objects; const [texts, setTexts] = useState([])
This is the getText method to be called in next :
const getText = async ()=>{
const res = await axios.get("http://localhost:3001/api/sth",{
params: {
ids: followings.map(i => i.id),
str_ids: followings.map(i => i.str_id)
},
paramsSerializer: params => {
return qs.stringify(params)
}
});
let str_ids_res = res.data.map(i => i.string_id)
let texts_res = res.data.map(i => i.texts)
console.log(texts_res)
const filtered_res = //some processing here on texts_res
/* eslint eqeqeq: 0 */
if(!arrayIsEqual(filtered_res,texts)){
console.log("Not equal")
// append arrays to a array state
setTexts((prevtexts) => prevtexts.concat([...filtered_res]))
}
}
await axios.get("http://localhost:3001/api/sth")
always return two different texts from DB so setTexts should always get called.
component is a card component from semantic UI tho.
const Texts = ({texts}) =>{
return (
<>
{texts.map(text =>(
<div key={text.id}>
<Card>
<Card.Content>
<Card.Description>
{text.full_text}
</Card.Description>
</Card.Content>
</Card>
</div>
))}
</>
);
}
InfiniteScroll component only fires Next once even though my datalength variable is setting correctly.
PS. the texts is empty initially so when I start the app, only 'Loading....' is called
After some experiments, passing height = {some height} as props to InfiniteScroll does the trick..
<div id="scrollableDiv" style={{ height: 300, overflow: "auto" }}>
<InfiniteScroll
dataLength={tweets.length}
next={handleScrolling}
hasMore={!isEnd}
loader={<h5>Loading...</h5>}
scrollThreshold={1}
height={300}
endMessage={
<p style={{textAlign:'center'}}><b>Yay! You've seen it all!</b></p>
}
scrollableTarget="scrollableDiv"
>
{<Tweets tweets = {tweets}/>}
</InfiniteScroll>
</div>
Please do not initialize 'texts' with an empty array, you must initialize the 'texts' array with some array of objects (for example 10 or 20).
In the initial render of your code (at the first time). you can just fire an API in
useEffect(()=>{
// make API call which your firing in the next call function
},[])
So your component gets initialization then after it will start behaving as expected.
For your understanding, Please refer to the sample example in "code sandbox"
https://codesandbox.io/s/elated-danny-k4hylp?file=/src/index.js

Select an item from dropdown and use that value in another component in React native

I'm just trying to do something like that (when user select an item, then navigate to another component):
const handleValueChange=(itemValue, itemIndex) =>setTypeValue(itemValue)
const onPress = () => {
try{
const topic = "Plant/type";
...
navigation.navigate('Air')
}catch(err){
console.log(err)
}
}
return (
<Picker
selectedValue={typeValue}
onValueChange={handleValueChange}
style={{ top: '21%', height: 50, width: 150 }}/>
<TouchableOpacity
style={styles.button}
onPress={()=> onPress()}
/>
)
Typically when we want to pass value between two component we use props :
<AirScreen typeofPlant={typeValue} />
But in this case I have o idea how can I do it without invoked AirScreen
Just do something like this:
navigation.navigate('RouteName', { /* params go here */ })
You might want to read the following documentation:
https://reactnavigation.org/docs/params/
You can do that like:
const onPress = () => {
try{
const topic = "Plant/type";
...
navigation.navigate('Air', {newTopic: topic}) //you can pass another value by separating with comma
}catch(err){
console.log(err)
}
}
Then you can fetch the same value in next screen like:
function NextScreen({ route, navigation }) {
const topic = route.params.newTopic
}
Hope this works for you.

Buttons and touchables on React Native lag with delay

I'm tying to create a basic e-shop in my application and it works fine. Every item in flatlist has 3 buttons ( TouchableOpacity ) to set quantity.
Those buttons are very slow: 1 second between clock and re-rendering . It looks like a long press but it's a simple click: this is a simple video to show you
And this is the code in detail:
class Shop extends React.Component {
...
selectItem = (item, typeButton) => {
if (item.qte >= 0) {
switch (typeButton) {
case 'plus':
if (parseFloat(item.EshopPrice) <= parseFloat(this.state.score)) {
this.setState({
score: parseFloat(this.state.score).toFixed(2) - parseFloat(item.EshopPrice).toFixed(2),
})
const actionSum = { type: "INCREASE_SUM", value: item }
this.props.dispatch(actionSum)
} else {
this.showToast();
}
break;
case 'minus':
if (this.props.totaleQte > 0) {
item.qte = item.qte - 1
this.setState({
score: Number(parseFloat(item.EshopPrice).toFixed(2)) + this.state.score,
})
const actionSumMoin = { type: "DECREASE_SUM", value: item }
this.props.dispatch(actionSumMoin)
}
break;
case 'plus+':
if (parseFloat(item.EshopPrice) <= parseFloat(this.state.score)) {
item.qte = item.qte + 1
this.setState({
score: parseFloat(this.state.score).toFixed(2) - parseFloat(item.EshopPrice).toFixed(2),
})
const actionSum = { type: "SET_CURRENTSALE", value: item }
this.props.dispatch(actionSum)
} else {
this.showToast();
}
break;
default:
break;
}
}
};
...
render ()
...
return (...)
}
I call this function in a functional component in the same file which is the renderItem of a flatlit :
renderItem = ({ item }) => {
return (
<View style={StylesGift.buttonsContainer}>
{
item.qte === 0 ?
<TouchableOpacity
onPress={() => this.selectItem(item, 'plus+')}>
<Text style={[StylesGift.itemQte, StylesGift.roundItemQte]}>+</Text>
</TouchableOpacity>
:
<View style={StylesGift.buttonsQteContainer}>
<TouchableOpacity onPress={() => this.selectItem(item, 'minus')}>
<Text style={[StylesGift.itemQte, StylesGift.roundItemQte]}>-</Text>
</TouchableOpacity>
<Pressable
onPress={() => this.showModal(true, item)}>
<Text style={StylesGift.itemQte}>{item.qte}</Text>
</Pressable>
<TouchableOpacity onPress={() => this.selectItem(item, 'plus')}>
<Text style={[StylesGift.itemQte, StylesGift.roundItemQte]}>+</Text>
</TouchableOpacity>
</View>
}
</View>
)
}
I think the problem is in the setState({score: ...}) and dispatching action to redux, because when I remove them all or remove one of them, the click becomes very fast and smooth.
This is the treatment on the reducer:
case 'INCREASE_SUM':
const productShopIndex = state.Data.findIndex(item => item.ProductID === action.value.ProductID)
state.Data[productShopIndex].qte = state.Data[productShopIndex].qte + 1
nextState = {
...state,
sum: state.sum + parseFloat(action.value.EshopPrice),
}
return nextState || state
case 'DECREASE_SUM':
nextState = {
...state,
totaleQte: action.value.qte === 0 ? state.totaleQte - 1 : state.totaleQte,
sum: state.sum - parseFloat(action.value.EshopPrice),
}
return nextState || state
Actually the problem is you are directly dispatching the global actions while pressing the buttons. The side effect of this is when you press + or -, the reducer will take some time to do computation and change the state(Thats why theres delay there since the JS thread is blocked). The simplest solution to this is for every counter, make the increment or decrement as local state and inside useEffect(or componentDidUpdate) sync that count with reducer after some debounce. The flow is:
Store the count value in local state inside the counter
Make debounce of like 500ms so that when user presses any button within this time,it will ignore the last count and only update when user leaves counter after the debounce time.
And sync with the global reducer after the debounce.
I came across similar situation recently. So hope this helps.

How to refer to a single element of a mapped array to render a View

I've got an array of object. I mapped it to render it like a list with a label and an icon button.
I need to show another View below the element of the row when I hit the button.
The array of object is a let variable. I would have like to make it as a state (because of the re-rendering when a function setState is executed), but I need it as a let variable because they are datas coming from a navigation route
My idea was to add a flag in every object of the array to indicate when the View has to be rendered. This would have been false at the beginning and true when I hit the button, but nothing happen. While if don't use a flag when I hit the button, the View is rendered in every row
state={
visible:false
}
getNewView = (item) => {
if (this.state.visible && item.showViewBelow)
return (
<View>
<HiddenViewComponent/>
</View>
)
};
btnHandler = (item) => {
item.showViewBelow = true;
this.setState({visible:true})
}
render(){
const {navigation} = this.props;
const arrayNav = navigation.getParam('data', '');
let array = [
{
id:'0',
label: arrayNav.label1,
showViewBelow: false
},
{
id:'1',
label: arrayNav.label2,
showViewBelow: false
},
{
id:'2',
label: arrayNav.label3,
showViewBelow: false
}
]
return(
<View>
array.map((item, key)=> { return
<View key = {key} style={{marginHorizontal: 25}}>
<Text>{item.label}</Text>
<TouchableOpacity onPress={this.btnHandler(item)}>
<Image source=(blablabla)/>
</TouchableOpacity>
{this.getNewView(item)}
</View>)
)}
</View>
)
}
So, when i click in a row of the list, I want a new view to be showed below THIS row.
I tried to switch to true the flag in one of the object and leave it to false in the others. It works fine on that line! When I click the button in that row, the new view is correctly rendered below that row.
I tried also using a Flatlist and put some extradata with a state variable to be updated with a setState after the return of the new view. (just to try to re-render the page).
Still, I tried to put this.forceUpdate() in the getNewView method. Nothing happen. Nothing is rendered
So I think the problem is I don't re-render the whole screen, because having the object as let variable, this doesn't happen.
Have any ideas?
You can create method in constant utility to define defaultArray like
getDefaultArray(arrayNav){
return [
{
id:'0',
label: arrayNav.label1,
showViewBelow: false
},
{
id:'1',
label: arrayNav.label2,
showViewBelow: false
},
{
id:'2',
label: arrayNav.label3,
showViewBelow: false
}
];
}
In your component code
import Constants from "./Constants"
ComponentDidMount(){
const {navigation} = this.props;
const arrayNav = navigation.getParam('data', '');
this.state={
visible:false,
array = Constants.getDefaultArray(arrayNav);
}
}
getNewView = (item) => {
if (this.state.visible && item.showViewBelow)
return (
<View>
<HiddenViewComponent/>
</View>
)
};
btnHandler = (item, index) => {
const {navigation} = this.props;
const arrayNav = navigation.getParam('data', '');
let newData = Constants.getDefaultArray(arrayNav);
newData[index].showViewBelow = true;
this.setState({visible:true , array:newData})
}
render(){
return(
<View>
this.state.array.map((item, key)=> { return
<View key = {key} style={{marginHorizontal: 25}}>
<Text>{item.label}</Text>
<TouchableOpacity onPress={this.btnHandler(item,key)}>
<Image source=(blablabla)/>
</TouchableOpacity>
{this.getNewView(item)}
</View>)
)}
</View>
)
}

How to get currently visible index in RN flat list

I have a horizontal flat list where each item is width:300
All I am trying to do is to get index of currently visible item.
<FlatList
onScroll={(e) => this.handleScroll(e)}
horizontal={true}
data={this.state.data}
renderItem...
Tried this:
handleScroll(event) {
let index = Math.ceil(
event.nativeEvent.contentOffset.x / 300
);
And something like this:
handleScroll(event) {
let contentOffset = event.nativeEvent.contentOffset;
let index = Math.floor(contentOffset.x / 300);
but nothing is accurate I always get one index up or one index down.
What am I doing wrong and how to get correct current index in flat list?
For example I get to list item that is 8th in a list but I get index 9 or 10.
UPD. thanks to #ch271828n for pointing that onViewableItemsChanged shouldn`t be updated
You can use FlatList onViewableItemsChanged prop to get what you want.
class Test extends React.Component {
onViewableItemsChanged = ({ viewableItems, changed }) => {
console.log("Visible items are", viewableItems);
console.log("Changed in this iteration", changed);
}
render () => {
return (
<FlatList
onViewableItemsChanged={this.onViewableItemsChanged }
viewabilityConfig={{
itemVisiblePercentThreshold: 50
}}
/>
)
}
}
viewabilityConfig can help you to make whatever you want with viewability settings. In code example 50 means that item is considered visible if it is visible for more than 50 percents.
Config stucture can be found here
With related to #fzyzcjy's and #Roman's answers. In react, 16.8+ you can use useCallback to handle the changing onViewableItemsChanged on the fly is not supported error.
function MyComponent(props) {
const _onViewableItemsChanged = useCallback(({ viewableItems, changed }) => {
console.log("Visible items are", viewableItems);
console.log("Changed in this iteration", changed);
}, []);
const _viewabilityConfig = {
itemVisiblePercentThreshold: 50
}
return <FlatList
onViewableItemsChanged={_onViewableItemsChanged}
viewabilityConfig={_viewabilityConfig}
{...props}
/>;
}
Many thanks to the most-voted answer :) However, it does not work when I try it, raising an error saying that changing onViewableItemsChanged on the fly is not supported. After some search, I find the solution here. Here is the full code that can run without error. The key point is that the two configs should be put as class properties instead of inside the render() function.
class MyComponent extends Component {
_onViewableItemsChanged = ({ viewableItems, changed }) => {
console.log("Visible items are", viewableItems);
console.log("Changed in this iteration", changed);
};
_viewabilityConfig = {
itemVisiblePercentThreshold: 50
};
render() {
return (
<FlatList
onViewableItemsChanged={this._onViewableItemsChanged}
viewabilityConfig={this._viewabilityConfig}
{...this.props}
/>
</View>
);
}
}
this.handleScroll = (event) => {
let yOffset = event.nativeEvent.contentOffset.y
let contentHeight = event.nativeEvent.contentSize.height
let value = yOffset / contentHeight
}
<FlatList onScroll={this.handleScroll} />
Get the round-off value to calculate the page-number/index.
In your case, I guess, you might ignore the padding or margin of items. The contentOffsetX or contentOffsetY should be firstViewableItemIndex * (itemWidth + item padding or margin).
As other answers, onViewableItemsChanged would be a better choice to meet your requirement. I wrote an article about how to use it and how it is implemented.
https://suelan.github.io/2020/01/21/onViewableItemsChanged/
A bit late, but for me was different with functional components...
If you are using functional component do it like:
const onScroll = useCallback((event: NativeSyntheticEvent<NativeScrollEvent>) => {
const slideSize = event.nativeEvent.layoutMeasurement.width;
const index = event.nativeEvent.contentOffset.x / slideSize;
const roundIndex = Math.round(index);
console.log("roundIndex:", roundIndex);
}, []);
Then on your FlatList
<FlatList
data={[2, 3]}
horizontal={true}
pagingEnabled={true}
style={{ flex: 1 }}
showsHorizontalScrollIndicator={false}
renderItem={renderItem}
onScroll={onScroll}
/>
While onViewableItemsChanged works for some conditions, it gives the
Error: Changing onViewableItemsChanged on the fly is not supported
error when ever you try to set the state.
This issue is fixed by using viewabilityConfigCallbackPairs instead of onViewableItemsChanged.
import React, {useRef} from 'react';
const App = () => {
// The name of the function must be onViewableItemsChanged.
const onViewableItemsChanged = ({viewableItems}) => {
console.log(viewableItems);
// Your code here.
};
const viewabilityConfigCallbackPairs = useRef([{onViewableItemsChanged}]);
return (
<View style={styles.root}>
<FlatList
data={data}
renderItem={renderItem}
keyExtractor={item => item.id}
viewabilityConfigCallbackPairs={
viewabilityConfigCallbackPairs.current
}
/>
</View>
);
}
Checkout this link for more details.
this will solve
Invariant Violation: Changing viewabilityConfig on the fly is not
supported
, and type script error on viewabilityConfigCallbackPairs.
const onViewableItemsChanged = ({ viewableItems }) => {
console.log(viewableItems)
};
const viewabilityConfig = { itemVisiblePercentThreshold: 100 };
const viewabilityConfigCallbackPairs = useRef([
{ viewabilityConfig, onViewableItemsChanged },
]);
<FlatList
...
viewabilityConfigCallbackPairs={
viewabilityConfigCallbackPairs.current
}
//viewabilityConfig={{ itemVisiblePercentThreshold: 100, }}<--------remove this
/>
typeScript:
import {ViewToken} from "react-native";
const onViewableItemsChanged = ({viewableItems}: {viewableItems: ViewToken[]}) => {
...
Kindly use class-based components. onViewableItemsChanged() method only works with classes otherwise it will give an error

Categories

Resources