How to get ref from custom component in react hook? - react-native

I have this code, using React.useRef() but not working:
Main.js:
import * as React from "react"
export const Main: React.FunctionComponent<Props> = observer((props) => {
const ref = React.useRef()
React.useEffect(() => {
///Can not get ref from message
ref.gotoPosition(5)
}, [])
return (
<View style={styles.container}>
<Message
ref={ref}
getGotoIndex={getFunction}
onEndList={isShowQuickMove}
isSpeaker={state.isSpeaker}
questionsList={state.questionsList}
clickQuestion={clickQuestion}
isTyping={chatStore.loading}
data={state.data}/>
</View>
)
}
Message.js:
import * as React from "react"
// eslint-disable-next-line react/display-name
export const Message = React.forwardRef((props, ref) => ({
const { ... } = props
const gotoPosition = (index) => {
console.log('in here')
}
return (
<View>
....
</View>
)
}
)
I can not get ref from Message, even i used React.forwardRef. How to access gotoPosition function in Message by ref like ref.gotoPosition(5). Thanks

You are not passing the ref you get to the Flatlist all you need to do is pass it like so:
<FlatList
ref={ref} // create a referenece like so
extraData={[data, isSpeaker]}
onEndReached={handleEnd}
onEndReachedThreshold={0.4}
data={data}
keyExtractor={(item, index) => index.toString()}
renderItem={renderItems}
/>

Related

invalid hook call in mobx+react native

I'm new to mobx,
I was told that I can't use directly rootStore from rootStore.tsx directly, and I have to replace it with hook, so I've tried to call hook useStore from rootStore.tsx
but in this case I've got an error "invalid hook call. Hooks can be called inside of the body"
my files are:
rootStore.tsx
import { createContext, useContext } from 'react'
import { makeAutoObservable } from 'mobx'
import { AsyncTrunk } from 'mobx-sync'
import AsyncStorage from '#react-native-async-storage/async-storage'
import { DayStyle, firstDayStyle } from '../styles/markedDayStyle'
const period: Record<string, DayStyle> = {
'2022-02-16': firstDayStyle,
}
export const rootStore = makeAutoObservable({
periods: period,
})
export const trunk = new AsyncTrunk(rootStore, {
storage: AsyncStorage,
})
export const StoreContext = createContext(rootStore)
export const StoreProvider = StoreContext.Provider
export const useStore = () => useContext(StoreContext)
App.tsx
const App = observer(({}) => {
const store = useStore()
const [isStoreLoaded, setIsStoreLoaded] = useState(false)
useEffect(() => {
const rehydrate = async () => {
await trunk.init()
setIsStoreLoaded(true)
}
rehydrate().catch(() => console.log('problems with localStorage'))
}, [store])
if (!isStoreLoaded) {
return (
<View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
<ActivityIndicator size="large" />
</View>
)
} else {
return (
<StoreProvider value={store}>
<PaperProvider theme={store.isDarkMode ? darkTheme : lightTheme}>
<View style={styles.container}>
<CalendarScreen/>
</View>
</PaperProvider>
</StoreProvider>
)
}
})
CalendarScreen.tsx
const CalendarScreen = observer(({}) => {
const store = useStore()
const handleDayPress = (day: DateData) => {
setModalVisible(true)
setPressedDay(day.dateString)
}
return (
<SafeAreaView style={styles.screenContainer}>
<Calendar
onDayPress={day => {handleDayPress(day)}}
/>
<View>
<ModalConfirmDay modalVisible={modalVisible} setModalVisible={setModalVisible} pressedDay={pressedDay} />
</View>
</SafeAreaView>
)
)}
ModalConfirmDay.tsx
import { fillMarkedDays } from '../functions/fillMarkedDays'
const ModalConfirmDay = observer(({ modalVisible, setModalVisible, pressedDay }: ModalConfirmDayProps) => {
const handlePeriodStarts = () => {
fillMarkedDays(pressedDay)
setModalVisible(false)
}
return (
<View style={styles.centeredView}>
<Modal
visible={modalVisible}
>
<View style={styles.modalView}>
<TouchableOpacity onPress={() => handlePeriodStarts()}>
<Text>Period starts</Text>
</TouchableOpacity>
</View>
</Modal>
</View>
)
})
fillMarkedDays.tsx
import { rootStore, useStore} from '../store/rootStore'
import { firstDayStyle} from '../styles/markedDayStyle'
const fillMarkedDays = (selectedDay: string) => {
const store = useStore()
if (selectedDay) {
store.periods[selectedDay] = firstDayStyle
}
}
when I try to add a new key-value (in fillMarkedDays.tsx) to store.periods I'm getting this
how can I fix this or select a better approach to call the store? Thanks everyone
By the rules of hooks you can't use hooks outside of the body of the function (component), so basically you can only use them before return statement, and also you can't use any conditions and so on. fillMarkedDays is just a function, not a component, it has no access to React context, hooks or whatever.
What you can do is first get the store with hook, then pass it as an argument into the fillMarkedDays function:
const ModalConfirmDay = observer(({ modalVisible, setModalVisible, pressedDay }: ModalConfirmDayProps) => {
const store = useStore()
const handlePeriodStarts = () => {
fillMarkedDays(store, pressedDay)
setModalVisible(false)
}
// ...
}

react native usememo renderitem not working why?

I want to prevent unneccessary rerender, so I use useMemo.
But I got this error message:
TypeError: renderItem is not a function. (In 'renderItem(props)', 'renderItem' is an instance of Object)
Code:
import * as React from 'react';
import { StyleSheet, Text, View, TouchableOpacity, Image, Dimensions, FlatList } from 'react-native';
import faker from 'faker';
const { width, height } = Dimensions.get('window');
const Advertising = () => {
const data = [
{ id: '1', name: 'Jens', image: faker.image.avatar() },
{ id: '2', name: 'Günther', image: faker.image.avatar() }
];
const renderItem = React.useMemo(() => {
return (
<View>
<Text>Hello</Text>
</View>
)
}, [data]);
return (
<FlatList
data={data}
keyExtractor={item => Math.random(100).toString()}
renderItem={renderItem}
/>
)
};
const styles = StyleSheet.create({
container: {
flex: 1,
}
});
export default React.memo(Advertising);
......................................................................................................................................................................................................
useMemo is a react hook and react hooks can't be used in that way.
I would advice you create a separate component for the this.
const MyComponent = React.memo(({item})=>{
return (<View></View>);
});
and then import like so
const renderItem = ({item}) => {
return <MyComponent />
}
...
<FlatList
data={data}
renderItem={renderItem}
keyExtractor={(_item, i)=>i.toString()}
/>
Also consider useCallBack
You have to return your renderItem function as a callback inside useMemo.
const renderItem = React.useMemo(() => () => (
<View>
<Text>Hello</Text>
</View>
), [data])
same as
const renderItem = () => (
<View>
<Text>Hello</Text>
</View>
)
const memoizedRenderItem = React.useMemo(renderItem, [data])

How to convert Fetch to Axios and Class component to Functional component?

How to convert Fetch to Axios and Class component to Functional component?
I want to learn to implement Infinite-Scrolling using functional component and axios in React native, but it is difficult to apply because the reference document is composed of class component and fetch.
import React from 'react';
import {
View,
Image,
Text,
FlatList, // here
} from 'react-native';
export default class App extends React.Component {
state = {
data: [],
page: 1 // here
}
_renderItem = ({item}) => (
<View style={{borderBottomWidth:1, marginTop: 20}}>
<Image source={{ uri: item.url }} style={{ height: 200}} />
<Text>{item.title}</Text>
<Text>{item.id}</Text>
</View>
);
_getData = () => {
const url = 'https://jsonplaceholder.typicode.com/photos?_limit=10&_page=' + this.state.page;
fetch(url)
.then(r => r.json())
.then(data => {
this.setState({
data: this.state.data.concat(data),
page: this.state.page + 1
})
});
}
componentDidMount() {
this._getData();
}
// here
_handleLoadMore = () => {
this._getData();
}
render() {
return (
<FlatList
data={this.state.data}
renderItem={this._renderItem}
keyExtractor={(item, index) => item.id}
onEndReached={this._handleLoadMore}
onEndReachedThreshold={1}
/>
);
}
}
When converting from a class to a function component, there are a few steps which are relevant here:
replace lifecycle events like componentDidMount with useEffect.
replace component state with one or many useState hooks.
convert class methods to plain functions.
remove all references to this.
delete render() and just return the JSX directly.
The methods _renderItem, _getData, and _handleLoadMore are basically unchanged. They just become const variables instead of class properties.
Here's the straight conversion from class to function component:
import React, { useEffect, useState } from 'react';
import {
View,
Image,
Text,
FlatList,
} from 'react-native';
export default function App() {
const [page, setPage] = useState(1);
const [data, setData] = useState([]);
const _renderItem = ({ item }) => (
<View style={{ borderBottomWidth: 1, marginTop: 20 }}>
<Image source={{ uri: item.url }} style={{ height: 200 }} />
<Text>{item.title}</Text>
<Text>{item.id}</Text>
</View>
);
const _getData = () => {
const url =
'https://jsonplaceholder.typicode.com/photos?_limit=10&_page=' + page;
fetch(url)
.then((r) => r.json())
.then((data) => {
setData(data.concat(data));
setPage(page + 1);
});
};
const _handleLoadMore = () => {
_getData();
};
// useEffect with an empty dependency array replaces componentDidMount()
useEffect(() => _getData(), []);
return (
<FlatList
data={data}
renderItem={_renderItem}
keyExtractor={(item, index) => item.id}
onEndReached={_handleLoadMore}
onEndReachedThreshold={1}
/>
);
}
Here it is with axios and with a few other improvements. I noticed that the end reached function was being called upon reaching the end of the initial zero-length list causing the first page to be fetched twice. So actually the componentDidMount is not needed. I changed from .then() to async/await, but that doesn't matter.
import React, { useEffect, useState } from 'react';
import { View, Image, Text, FlatList } from 'react-native';
import axios from 'axios';
export default function App() {
const [page, setPage] = useState(1);
const [data, setData] = useState([]);
const _renderItem = ({ item }) => (
<View style={{ borderBottomWidth: 1, marginTop: 20 }}>
<Image source={{ uri: item.url }} style={{ height: 200 }} />
<Text>{item.title}</Text>
<Text>{item.id}</Text>
</View>
);
const _getData = async () => {
const url =
'https://jsonplaceholder.typicode.com/photos?_limit=10&_page=' + page;
const res = await axios.get(url);
setData(data.concat(res.data));
setPage(page + 1);
};
const _handleLoadMore = () => {
_getData();
};
// useEffect with an empty dependency array replaces componentDidMount()
useEffect(() => {
// put async functions inside curly braces to that you aren't returing the Promise
_getData();
}, []);
return (
<FlatList
data={data}
renderItem={_renderItem}
keyExtractor={(item, index) => item.id}
onEndReached={_handleLoadMore}
onEndReachedThreshold={1}
/>
);
}
Expo Link -- It works on my device, but the infinite scroll doesn't seem to work in the web preview.
There are additional more "advanced" improvements that you can make:
set state with a callback of previous state to ensure that values are always correct. setPage(current => current + 1) setData(current => current.concat(res.data))
memoization with useCallback so that functions like _renderItem maintain a constant reference across re-renders.
exhaustive useEffect dependencies (requires memoization).

React Native insert one component into another

Is there a way to "inject" one RN component into another in a specific place.
Say I have this component:
const Original = () => {
return (
<View>
<Text>Hello</Text>
{InsertChildComponentHere}
</View>
)
}
const ChildComponent = () => {
return (
<View>
<Text>I am a child component</Text>
</View>
)
}
To inject a Component into a other Component (HOC) your component has to accept "Component" as params. You can write it like this:
const Original = (Component) => {
const newComponent = ({ ...props }) => {
return (
<Fragment>
<Text>Hello</Text>
<Component {...props} />
</Fragment>
);
};
return newComponent;
};
to create the HOC you can write:
const MyComponent = withOriginal(ChildComponent);

Error when call a action come from a other file on react native

On index.js
...
import ButtonContent from './ButtonContent';
...
class App extends Component {
onAlert() {
alert("Test")
}
render() {
return (
<View>
<ButtonContent/>
</View>
)
}
}
file ButtonContent.js
...
import { withNavigation } from "react-navigation";
...
const ButtonContent = () => (
<TouchableOpacity onPress={() => {
this.onAlert();
}}>
<Text>Alert</Text>
</TouchableOpacity>
);
export default withNavigation(ButtonContent);
Error this.onAlert() is not function. How to fix it?
You need to pass onAlert function to ButtonContent component,
<ButtonContent onAlert={this.onAlert}/>
And then you can call this function using props,
const ButtonContent = (props) => (
<TouchableOpacity onPress={props.onAlert}>
<Text>Alert</Text>
</TouchableOpacity>
);
#ravibagul91 thanks your ideas, I has fixed ok. This is my edited code
file ButtonContent.js
type Props = {
onAlert: Function
};
const ButtonContent = ({ onAlert }: Props): Object => (
<TouchableOpacity onPress={props.onAlert}>
<Text>Alert</Text>
</TouchableOpacity>
);
And index.js
<ButtonContent onAlert={this.onAlert}/>