React Native different categories rendered to header component - javascript

I am developing a React Native quiz application. I already have developed the header component and it looks fantastic.
// import libraries for making a component
import React from 'react';
import { Text, View } from 'react-native';
// make a component
const Header = (props) => {
const { textStyle, viewStyle } = styles;
return (
<View style={viewStyle}>
<Text style={textStyle}>{props.headerText}</Text>;
</View>
);
};
const styles = {
viewStyle: {
backgroundColor: '#F8F8F8',
justifyContent: 'center',
alignItems: 'center',
height: 60,
paddingTop: 15,
shadowColor: '#000',
shadowOffset: { width: 0, height: 2 },
shadowOpacity: 0.2,
elevation: 2,
position: 'relative'
},
textStyle: {
fontSize: 20
}
};
// make a component available to other parts of the app
export default Header;
I am going to be doing an ajax request to an api that has a category and then one quiz question per category.
When I make a GET request to this endpoint I get back an array of objects and each object has a question category and one boolean question of true or false.
So in total I have 10 categories with a question for each category for a total of 10 questions.
So I need to figure out how to make an Ajax request or an http request from my mobile application to fetch this list of data and more critically, once I have the list of data, I need to figure out a way to get the category piece to render as the header.
This is the part I am stuck on.
I plan to have a component called QuestionList, the purpose of QuestionList will be to fetch the list of data or fetch the list of questions and once fetched, it will render several QuestionDetail components. So I will have a QuestionList and a QuestionDetail.
But again the api has a category for each question and I want to render that category content as the header for each question. So a different header each time the user goes to the next question.
So in the code above, rather than letting the header component decide what text should be displayed I refactored it slightly so that the app component will decide what text to show in there.
Did I just mess this up in terms of what I am trying to accomplish? Should I have let the header component decide what text should be displayed? And more importantly how do I get the header component or the App component to render the different categories? Should I be creating a separate CategoryList component and then a CategoryHeader component for each specific component that renders a different category from the api?

Based on your requirements you need a SectionList.
It already has a support for renderSectionHeader where you can integrate your Header component
An example to that would be
<SectionList
renderItem={({ item, index, section }) => <QuestionDetails {...item} />} // Render your Question Details Component here
renderSectionHeader={({ section: { title } }) => <Header {...title}/>} //... Render your Custom Header Component here
sections={[
{ title: 'Category1', data: ['question1', 'question2'] },
{ title: 'Category2', data: ['question1', 'question2'] },
{ title: 'Category3', data: ['question1', 'question2'] },
]}
keyExtractor={(item, index) => item + index} />
This is just a sample, you can modify to generate the array based on this that suits your requirements.

Related

Use data returned from API call outside of Promise.then() function

I am very puzzled on a relatively trivial question.
I wish to retrieve data from my database using a node.js API call, the API call is working perfectly fine, and I am able to successfully retrieve data in my .then(()=>{}) function in my promise.
However the issue I am having is that I wish to use this data outside of the scope of the "then" particularly I want to map the results into a ListViewItem in my front end.
Here is the code
import {Box, Button, Grid, List, ListItem, ListItemButton, ListItemText} from "#mui/material"
import {useEffect} from "react";
import axios from "axios";
function RequestApproval() {
let dataFromDb =[]
const result = axios.get("http://localhost:3001/all-user-addresses").then((response) => {
console.log(response.data.result); //This works perfectly fine
dataFromDb.concat(response.data.result)
})
console.log(dataFromDb) //This list always appears empty.
const data = result;
return (
<div style={{display: 'flex', justifyContent: 'center', alignItems: 'center', height: '100vh'}}>
<Box style={{borderRadius: '16px', backgroundColor: '#6ea6be'}}>
<List sx={{width: '600px', maxWidth: '1000px'}}>
{//My listview items from my API call go over here
}
</List>
</Box>
</div>
);
}
export default RequestApproval;
you need to use state for this problem. at first
const [list,setList]= useState([])
then in your RequestApproval function replace
dataFromDb.concat(response.data.result)
by this
setList(response.data.result)
and your jsx map through the list array.
You can simply utilize the useState hook.
Replace let dataFromDb =[] with const [data, setData] = useState().
Then instead of using dataFromDb.concat(response.data.result) use setData(response.data.result).
For more information look at the official docs

App crashing when using multiple 3d objects in react-native-gl-model-view

I want to use 2 '3d object models' from react-native-gl-model-view simultaneously on one screen. When I use a single 3d object it works fine but as soon as I use two or more than two, every 3d object model starts flickering, and after a couple of seconds app crashes.
Here is how it works with multiple 3d objects
Here is the code:
import React, {Component} from 'react';
import {StyleSheet, View, Animated} from 'react-native';
import ModelView from 'react-native-gl-model-view';
const AnimatedModelView = Animated.createAnimatedComponent(ModelView);
export default class Multiple extends Component {
constructor() {
super();
this.state = {
rotateZ: new Animated.Value(0),
};
}
componentDidMount() {
this.animate(0);
}
animate(iteration) {
Animated.timing(this.state.rotateZ, {
toValue: ++iteration * 360,
useNativeDriver: true,
duration: 5000,
}).start(this.animate.bind(this, iteration++));
}
renderModel() {
return (
<AnimatedModelView
model={{
uri: 'demon.obj',
}}
texture={{
uri: 'demon.png',
}}
tint={{r: 1.0, g: 1.0, b: 1.0, a: 1.0}}
animate
scale={0.01}
translateZ={-2.5}
rotateX={270}
rotateZ={Animated.add(this.state.rotateZ, Math.random() * 360)}
style={styles.model}
/>
);
}
render() {
return (
<View style={styles.container}>
<View style={styles.row}>
{this.renderModel()}
{this.renderModel()}
</View>
</View>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
},
row: {
flex: 1,
flexDirection: 'row',
},
model: {
flex: 1,
backgroundColor: 'transparent',
},
});
Note 1: To load a model on Android, you need to place the model in the android/app/src/main/assets folder. Create a new folder if it doesn't exist yet.
assets/models which were used in the above code.
If you want to try the code:
npm install react-native-gl-model-view --save
Add assets in location shown in Note 1.
create a file Muliple.js and paste above code
npm run android
Sorry, I wanted to show you the code in expo snack but it wasn't working and showing the following error:
requireNativeComponent: "RNGLModelView" was not found in the UIManager.
Sometime packages are not linked to the project or need to be rebuilt. If you sure did all of the steps for adding that , link again the package to the project:
react-native link react-native-gl-model-view
and if that not works, if you have android studio , reinstall the project on your emulator.

How do i pass data from one component to another in realtime?

I am trying to pass data that I pull from a SQLDatabase into another component that would display it. I'm not sure how to do it exactly...
In my App.js
This calls CustomList
import CustomList from './components/FlatList';
export default function App() {
return(
<CustomList />
);
};
which
In my CustomList
import Data from './Data';
...
export default function CustomList() {
//Sets up Getter , Setter , Initial Data
const [data, setData] = useState(Data);
...
return (
<FlatList
ListHeaderComponent = {header}
data = {data}
keyExtractor = { (item) => (item.id).toString()}
ItemSeparatorComponent = { () => <View style={styles.itemSeparator}></View>}
contentContainerStyle={ {borderBottomColor:'grey', borderBottomWidth: 1} }
renderItem = { ({item, index}) => <ListItem item={item} index={index}/>}
/>
...
The CustomList above works if I import hard-coded data below
In my Data.js
const Data = [
{id: 1, text: 'Boadb'},
{id: 2, text: 'Joe'},
{id: 3, text: 'Jane'},
{id: 4, text: 'John'},
{id: 5, text: 'Janet'},
{id: 6, text: 'Janet'},
{id: 7, text: 'Janet'},
{id: 8, text: 'Janet'},
];
export default Data;
However, I want a real-time syncing database that will update the CustomList whenever changes are made.
In my SQLData.js
let helperArray;
...
export default function SQLData() {
...
function querySuccess(tx, results) {
...
helperArray = [];
//Go through each item in dataset
for (let i = 0; i < len; i++) {
let row = results.rows.item(i);
helperArray.push(row);
}
...
return ();
};
As you can see from the code above, I have put the data pulled from the SQLDatabase into a variable helperArray. I was wondering how do I import it similarly like 'Data.js' and have it output the same way! Thanks
How do I pass data from one component to another in realtime?
There are several ways of doing this, one of which is through props. It technically is not real time, but it is close and probably fast enough for your use case.
https://reactjs.org/docs/components-and-props.html
Good news! You are already doing this here:
<FlatList
ListHeaderComponent = {header}
data = {data}
keyExtractor = { (item) => (item.id).toString()}
ItemSeparatorComponent = { () => <View style={styles.itemSeparator}></View>}
contentContainerStyle={ {borderBottomColor:'grey', borderBottomWidth: 1} }
renderItem = { ({item, index}) => <ListItem item={item} index={index}/>}
/>
Where you are passing data as a prop to FlatList referencing CustomList's data variable.
data = {data}
So you want helperArray's information in the CustomList component. You could utilize React's Lifecycle to fetch the information once in your App Component's (or CustomList's) mounting phase. The mounting phase is when an instance of a component is being created and inserted into the DOM:
If you need to load data from a remote endpoint, componentDidMount is a good place to instantiate the network request.
Once you received the data from the DB, set it to your Component's state. Then if you are loading it in the App Component change your return statement to be:
return(
<CustomList data = {helperArray(the reference to the state )}/>
);
Another question is
However, I want a real-time syncing database that will update the CustomList whenever changes are made.
So the answer to this is very tricky and will depend on your tech stack. You could host a back-end application that acts as a REST controller for your front end.
If you are not familiar it would look like this
Front End (React WebApp) makes changes to data and clicks submit (or any other event) ->
Front End will use a library(Axios.js is very popular with React) to make a HTTP request to the backend ->
BackEnd (Spring server / Node.js server, etc.) receives that HTTP request processes it, then creates a connection with a DB (SQL, Mongo, etc.)->
Back End will then use that connection to write to the database (if using Spring JPA, is using Node.js mssql) ->
Now with your updated data in your DB, the next time someone visits your front end application it will make a request to get the data from your back end then populate the page with that data.
Here is another great answer to a similar question

Make text become white upon changing value in react? React-spring?

I have a div, with player score, deaths and assists:
<div className="liveMatchPlayerScore">
{data.kill_count}/{data.death_count}/{data.assists_count}
</div>
Every time the kill or death count changes, I'd like the text to turn a bold white for 3 seconds.
I was thinking of using react-spring for this, specifically useTransitions, but the documentation shows examples using an array of items. I going to put each of the scores in an array, but it seems counterproductive.
Previously i tried wrapping the scores in an "Spring" component from react-spring but that only animated the scores on their initial render - not when they update.
How can I make the kill_count and death_count become white for 3 seconds upon changing value?
Thank you
I used #PeterAmbruzs solution, but i seem to be getting strange numbers. For example in the images below, first the score was 0/0/0 and the first number increased by 1. Instead of becoming 1/0/0, it became 01/0/0. I'm also getting absurdly high numbers for some reason. Does anyone know why this might be happening?
I have also a solution. I think it is quite simple. First you create a component for the animated numbers. I wrapped it in react.memo to update it only when its property change. You can see it is bold, and red at start, but after 3sec it became normal and black. But you can change the style whatever you want. I added skip property to prevent animation for example for the first render.
const FadeNumber = React.memo(({ value, skip }) => {
const style = useSpring({
from: { color: "red", fontWeight: "bold" },
to: { color: "black", fontWeight: "normal" },
delay: 3000
});
return (
<animated.span style={{ margin: "10px", ...(skip ? {} : style) }}>
{value}
</animated.span>
);
});
Now there is a trick to reRender the animation at property change. Simly put the value to the key. When the key changes a new component will be created, so the animation will be played again. I added a unique prefix to prevent side effects.
<FadeNumber skip={kill === 0} key={"a" + kill} value={kill} />
And the whole example:
https://codesandbox.io/s/react-spring-change-fade-out-j8ebk
https://stackblitz.com/edit/react-5sztov?file=index.js
setTimeout() is still a viable construct - even when working in React.
import React, { Component } from 'react';
import { render } from 'react-dom';
import './style.css';
class App extends Component {
constructor() {
super();
this.state = {
assistsCount: 0,
color: 'black',
deathCount: 0,
fontWeight: 'normal',
killCount: 0,
setStyleToReset: false,
};
}
increaseKillCount () {
this.setState((prevState, props) => {
return {
killCount: prevState.killCount + 1,
color: 'white',
fontWeight: 'bold',
setStyleToReset: true,
};
});
}
render() {
if (this.state.setStyleToReset) {
this.resetStyle();
}
return (
<div>
<div style={{
backgroundColor:'green',
}}>
<span style={{color:this.state.color, fontWeight:this.state.fontWeight}}>{this.state.killCount}</span>/{this.state.deathCount}/{this.state.assistsCount}
</div>
<button onClick={() => this.increaseKillCount()}>Increase Kill Count</button>
</div>
);
}
resetStyle() {
setTimeout(() => {
this.setState({
color: 'black',
fontWeight: 'normal',
});
}, 3000);
}
}
render(<App />, document.getElementById('root'));

React Native: TextInput with state and AsyncStorage

When typing on the keyboard I was seeing some warnings about the input being ahead of the JS code..
Native TextInput(react native is awesome) is 4 events ahead of JS - try to make your JS faster.
So added the debounce and got this to "work":
...
import { debounce } from 'lodash'
...
export default class App extends React.Component {
constructor(props) {
super(props)
this.state = {
data,
indexRef: data.reduce((result, item, index) => {
result[item.title] = index
return result
}, {}),
ready: false,
}
this.updatePitch = this.updatePitch.bind(this)
this.saveLocally = debounce(this.saveLocally, 300).bind(this)
}
...
updatePitch(id, text) {
// Copy the data
let data = [...this.state.data]
const index = data.findIndex(obj => obj.id == id)
data[index].pitch = text
// Update the state
this.setState({ data }, this.saveLocally(data))
}
saveLocally(data) {
try {
AsyncStorage.setItem('data', JSON.stringify(data))
this.forceUpdate()
} catch (error) {
// Well..
}
}
render() {
...
BTW: I'm doing some "prop drilling" for now - but planning to do use the Context API (react 16.3)
The warning seems to have gone by adding debounce (1).. But I'm seeing some strange issues - particularly on the iPhone 8 plus simulator (not seeing the same on iPhone 6 simulator or Android device)
Issues observed:
TextInput don't expand - it just add scolling (expands on iPhone 6 and Android device)
Some layout issues in the FlatList - seems like it has problems finding correct height of list elements..
What is the best practice for fast JS code and saving to both state and AsyncStorage?
(1) One other way than using debounce could be to use getDerivedStateFromProps and add some sort of timer pushing the state to the parent component after some period of time.. But wasn't sure that this would make the JS code faster. So didn't try it.
UPDATE (again)
I open sourced the entire code since it is too hard to give all the needed information in a SO post when the code is so nested.
The entire code is here:
https://github.com/Norfeldt/LionFood_FrontEnd
(I know that my code might seem messy, but I'm still learning..)
I don't expect people to go in and fix my code with PR (even though it would be awesome) but just give me some code guidance on how to proper deal with state and AsyncStorage for TextInput.
I know I have some style issues - would love to fix them, but also comply with SO and keep it on topic.
Update II
I removed forceUpdate and replaced FadeImage with just vanilla react native Image.
but I'm still seeing some weird issues
Here is my code
import React from 'react'
import {
StyleSheet,
SafeAreaView,
FlatList,
StatusBar,
ImageBackground,
AsyncStorage,
Platform,
} from 'react-native'
import SplashScreen from 'react-native-splash-screen'
import LinearGradient from 'react-native-linear-gradient'
import { debounce } from 'lodash'
import Section from './Section'
import ButtonContact from './ButtonContact'
import { data } from '../data.json'
export default class App extends React.Component {
constructor(props) {
super(props)
this.state = {
data,
indexRef: data.reduce((result, item, index) => {
result[item.title] = index
return result
}, {}),
ready: false,
}
}
async componentDidMount() {
SplashScreen.hide()
try {
let BusinessPlan = await AsyncStorage.getItem('BusinessPlan')
if (BusinessPlan !== null) {
// We have data!!
let data = JSON.parse(BusinessPlan)
data = this.state.data.map(item => {
const index = data.findIndex(obj => obj.id == item.id)
const pitch = index >= 0 ? data[index].pitch : ''
return { ...item, pitch }
})
this.setState({ data, ready: true })
} else {
this.setState({ ready: true })
}
} catch (error) {
// Error retrieving data
}
}
updatePitch = (id, text) => {
// Copy the data
let data = [...this.state.data]
const index = data.findIndex(obj => obj.id == id)
data[index].pitch = text
// Update the state
this.setState({ data }, this.saveLocally(data))
}
saveLocally = data => {
try {
AsyncStorage.setItem('BusinessPlan', JSON.stringify(data))
} catch (error) {
// Well..
}
}
render() {
return (
<LinearGradient
style={{ flex: 1 }}
start={{ x: 0.0, y: 0.25 }}
end={{ x: 0.5, y: 1.0 }}
colors={['#000000', '#808080', '#000000']}
>
<StatusBar
barStyle={'light-content'}
backgroundColor={Platform.OS == 'iOS' ? 'transparent' : 'black'}
/>
<SafeAreaView>
<ImageBackground
source={require('../images/BackgroundImage.png')}
style={{ width: '100%', height: '100%' }}
resizeMode={'cover'}
>
<FlatList
data={this.state.data}
initialNumToRender="16"
keyExtractor={item => item.id}
renderItem={({ item }) => (
<Section
id={item.id}
title={item.title}
pitch={item.pitch}
updatePitch={debounce(this.updatePitch, 1000)}
questions={item.questions}
ready={this.state.ready}
/>
)}
ListFooterComponent={<ButtonContact />}
style={{
backgroundColor: 'transparent',
borderColor: '#000',
borderWidth: StyleSheet.hairlineWidth,
}}
/>
</ImageBackground>
</SafeAreaView>
</LinearGradient>
)
}
}
const styles = StyleSheet.create({
sectionHeader: {
fontSize: 24,
marginHorizontal: 5,
},
})
(I also updated my git repo)
Update III
It seems that the setup I have for state and AsyncStorage works fine with a debounce. The issues I was seeing was because I'm draining the CPU (next step to fix).
I tried your code:
"I'm seeing some strange issues - particularly on the iPhone 8 plus
simulator (not seeing the same on iPhone 6 simulator or Android
device)" ==> I confirmed this
The app takes about 100% CPU.
After a while trying I figured out:
"I'm seeing some strange issues - particularly on the iPhone 8 plus
simulator (not seeing the same on iPhone 6 simulator or Android
device)" ==> doesn't right, just wait a little TextInput will expand.
There are nothing wrong with state and AsyncStorage. I didn't get any warning.
The root issue is the animation in FadeImage :
The app render many Carousel, and each Carousel has AngleInvestor, and FadeImage. The problem is FadeImage
FadeImage run Animated with duration 1000 => CPU is overloaded
==> That why TextInput add scroll then take a long time to expand, and FlatList look like has problem, but not. They are just slowly updated.
Solution:
Try to comment FadeImage, you will see the problem gone away.
Don't start so many animations as the same time. Just start if it appears (Ex: the first card in Carousel )
UPDATE
I got your problem: typing fastly cause setState call so many times.
You use can debounce for this situation:
In App.js
render() {
console.log('render app.js')
...
<Section
id={item.id}
title={item.title}
pitch={item.pitch}
updatePitch={debounce(this.updatePitch, 1000)} // the key is here
questions={item.questions}
ready={this.state.ready}
/>
You can change the delay and watch the console log to see more. As I tried, delay about 500 can stop the warning.
P/s: You should try to remove forceUpdate

Categories

Resources