Is it OK to pass a lot of props to a child component (redux) - react-native

I have a component that has a child component within it.
This child component needs to access to the state, and dispatch a lot of actions. So I ended up having a component that takes all these properties through his own props.
render(){
const menu = <Menu
navigator={navigator}
userTracks={this.props.tracks}
currentTrackID={this.props.currentTrack.id}
onAddNewTrack={() => {this.props.addNewTrack()}}
onEditTrack={(track) => {this.props.onEditTrack(track)}}
onClearBeacons={(track) => {this.props.onClearBeacons(track)}}
onDeleteTrack={(track) => {this.props.onDeleteTrack(track)}}/>;
return(
<SideMenu
menu={menu}
isOpen={this.props.sideMenuOpened}
onChange={(state) => {this.props.changeSideMenuOpened(state)}}
menuPosition='right'>
...more stuff...
</SideMenu>
);
I rely a lot on this component, and it is highly probable that the list of props will keep on growing again and again.
Is there any cleaner way to access the store in a child component other than through props ?
Is it okay to do it this way ?

You can reduce the code in the following manner:
onClick(type, data) {
switch (type) {
case 'addNewTrack':
this.props.addNewTrack();
break;
case 'editTrack':
this.props.onEditTrack(data.track);
break;
case 'clearBeacons':
this.props.onClearBeacons(data.track);
break;
case 'deleteTrack':
this.props.onDeleteTrack(data.track);
break;
default:
break;
}
}
render() {
const menuProps = {
navigator: navigator,
userTracks: this.props.tracks,
currentTrackID: this.props.currentTrack.id,
onClick: this.onClick
}
const menu = <Menu {...menuProps} />
return (
<SideMenu
menu={menu}
isOpen={this.props.sideMenuOpened}
onChange={(state) => { this.props.changeSideMenuOpened(state) }}
menuPosition='right'>
...more stuff...
</SideMenu>
);
}
As you can see the data props are combined as an object and spread onto the <Menu /> component. For the multiple methods, I am only passing a single click. Menu component will send the type and data that each represents the various clicks.
Hope this helps :)

Related

React native flatlist rerender

I'm working on a flatlist that has complex child that cause expensive rerender, I need to optimize that but I'm not able to stop the rerendering with useMemo, please help me to go through this.
Here my list code:
<FlatList
data={thePosts}
extraData={thePosts}
keyExtractor={(item, index) => index.toString()}
removeClippedSubviews={true}
maxToRenderPerBatch={5}
updateCellsBatchingPeriod={30}
initialNumToRender={11}
windowSize={5}
refreshing={isRefreshing}
onRefresh={handleOnRefresh}
onEndReached={isLoading ? () => null : () => getPosts("more")}
onEndReachedThreshold={0.1}
renderItem={memoizedPost}
//renderItem={renderThePost}
ItemSeparatorComponent={renderThePostSep}
ListFooterComponent={renderThePostListFooter}
/>
here the renderPost:
const renderThePost = (props) => {
let post = props.item;
if (post[0].type == "share") {
return (
<TheSharedPost thePost={post} />
);
} else {
return <ThePost thePost={post} />;
}
};
I've tried to use memoization like this:
const memoizedPost = useMemo(() => renderThePost, []);
Now the problem is, the empty array as useMemo argument I think that only accept the first render but not working, I've tried to use [item.someProperty] but I'm not able to recognize item in the argument (item is not defined)
I've also used useCallback but still no luck, a lot o rerendering happen. Please help me to fix this. Tnz
you can use React.memo to avoid rendering of flatlist items
function TheSharedPost(props) {
/* render using props */
}
export default React.memo(TheSharedPost);
function ThePost(props) {
/* render using props */
}
export default React.memo(ThePost);

Child component not rerendered on prop change

In the following code, I expect OfferList to rerender when I add an offer item to the store. OfferList itself is not an observable, but the offer array is passed as a prop.
export const MerchantScreen: FC = observer(() => {
const { merchantStore } = useStores()
return (
<View>
<OfferList data={merchantStore.offers} />
<View>
<Button title={"New Offer"} onPress={() => merchantStore.addOffer()}/>
</View>
</View>
)
})
export const OfferList: FC<OfferListProps> = ({ data }: OfferListProps) => {
const renderItem = (offer: ListRenderItemInfo<any>) => {
return (
<Text>{offer.name}</Text>
)
}
return (
<FlatList
data={data}
renderItem={renderItem}
keyExtractor={(item) => item.id}
/>
)
}
I use Mobx State Tree. All merchantStore.addOffer() does for now is push another offer item into the array.
What I tried / findings:
When I read from the store in my MerchantScreen, e.g. by adding
<Text>{ merchantStore.offers.toString() }</Text>
, the OfferList will also update. I suspect that reading from the store directly in the parent component will force a rerender of the child component as well.
I stumbled upon some answers here that would indicate that a missing key attribute within the FlatList renderItems could be the issue. Tried using key={item.id} to no avail. Also, as you can see I use the keyExtractor prop of FlatList.
Another answers suggested introducing local state to the component like this:
export const OfferList: FC<OfferListProps> = ({ data }: OfferListProps) => {
const [offers, setOfferss] = useState()
useEffect(() => {
setOffers(data)
}, [data])
const renderItem = (offer: ListRenderItemInfo<any>) => {
return (
<Text>{offer.name}</Text>
)
}
return (
<FlatList
data={offers}
renderItem={renderItem}
keyExtractor={(item) => item.id}
/>
)
}
This does not work and my gutfeeling is that this is not how it's done.
As you see my MerchantScreen parent component is an observable while my child component OfferList is not. From my expectation, the observer on my parent component should be enough. The parent component should already detect the change in the store and rerender. The child component in itself does not even use stores.
Overall, the problem at hand seems quite trivial so I guess I am just missing out on an important detail.
MobX only tracks data accessed for observer components if they are directly accessed by render, so if you want to react to each of the offers you need to access them somewhere. You sort of did when you tried merchantStore.offers.toString(), that's why it worked.
So first of all you need to make OfferList an observer.
But then you have FlatList which is native component and you can't make it an observer. What you can do is to access each offers item inside OfferList (just to subscribe for updates basically) like that data={offers.slice()} or even better with MobX helper method toJS data={toJS(offers)}
Depending on your use case you might also want to use <Observer> inside renderItem callback:
const renderItem = (offer: ListRenderItemInfo<any>) => {
return (
<Observer>{() => <Text>{offer.name}</Text>}</Observer>
)
}

React-select Clear and Dropdown indicators order

Thank the author for this library, but i have one question:
How to change order of Clear and Dropdown indicators (i want: Dropdown - first, Clear - second)?
I know this indicators are in children prop, but how change order on render?
Or how I can add additional button for clear after Dropdown indicator?
ClearIndicator and DropdownIndicator both are children of the IndicatorsContainer so that would be where the updates would have to be made.
One approach would be to first render a DropdownIndicator, then loop through the IndicatorContainer's children to find the DropdownIndicator, and remove it.
import React, { Children } from "react";
import Select, { components } from "react-select";
const IndicatorsContainer = ({ children, ...props }) => {
const allowedChildren = Children.map(children, (child) => {
return (child && child.type.name !== 'DropdownIndicator') ? child : null;
});
return (
<components.IndicatorsContainer {...props}>
<components.DropdownIndicator />
{allowedChildren}
</components.IndicatorsContainer>
);
};
const CustomSelect = (props) => (
<Select components={{ IndicatorsContainer }} {...props} />
)
export default CustomSelect;
Note: I found this SO question from the react-select issues page here where the user asked to move the DropdownIndicator and IndicatorSeparator to the left of the input which is a bit more complex but has a working codesandbox solution.

React Native ios Switch in FlatList not toggling after value changed

I am trying to toggle ios Switch in react native. But the switch comes back to initial position as soon as I change it.
What I have:
class ABC extends Component {
constructor(props) {
super(props)
this.state = {
obj: []
}
}
fetch(){
// fetch something from remote server, set it to state object array
}
setStatus(id, value){
var temp = [...this.state.obj]
temp.map((t) => {
if (t.id == id) {
t.flag = value
}
})
this.setState({ obj: temp })
}
render() {
return (
<View>
<FlatList
data={this.state.obj}
renderItem={({ item }) =>
<View>
<Text>{item.name}</Text>
<Switch
onValueChange={(val) => this.setStatus(item.id, val)}
value={item.flag}
/>
</View>
}
keyExtractor={({ id }, index) => id.toString()}
/>
</View>
);
}
}
I logged the before and after value of obj state and they seem to update. Should the FlatList be rendered again (like a web page refresh) ? Or is there something I am missing ? Searched SO for answers, couldn't find my mistake.
Flatlist has a prop called extraData.
This prop tells Flatlist whether to re-render or not.
If data in extraData changes then flatlist re-renders based on new data provided in data prop.
So whenever you need to re-render flatlist just change something in extraData.
Best way is to pass state toextraData which is passed to Data.
So, just pass extraData={this.state.obj}.
there also other way called forceUpdate.
you can call this.forceUpdate().
but this is not recommended because this will render not only flatlist but entire component in which you are calling this.

React Native passing functions with arguments as props

From what I have read its best to try and structure react apps with as many components as "dumb" renderers. You have your containers which fetch the data and pass it down to the components as props.
That works nicely until you want to pass functions down the chain that require arguments other than events.
class MyClass extends Component {
_onItemPress (myId) {
// do something using myId
}
render () {
return <MyComponent myID={10} onPress={(myId) => this._onItemPress(myId)} />
}
}
If I simply pass that as my onPress handler to MyComponent it won't return myId when called. To get around this I end up doing something like this.
export default ({myId, onPress) => {
const pressProxy = () => {
onPress(myId)
}
return (
<TouchableHighlight onPress={pressProxy}>
<Text>Click me to trigger function</Text>
</TouchableHighlight>
)
}
Am I doing this completely incorrectly? I would like to be able to have a simple component that I can re-use for list items where its sole function is to take a title, onpress function and return a list item to be used in ListViews renderRow function. Many of the onPress functions will require variables to be used in the onPress however.
Is there a better way?
The proper syntax would be something like this:
render () {
let myId = 10;
return <MyComponent myID={myId} onPress={() => this._onItemPress(myId)} />
}
Also, if you plan to use this inside _onItemPress (for example to call other methods in MyClass), you need to bind the scope like this:
render () {
let myId = 10;
return <MyComponent
myID={myId}
onPress={() => this._onItemPress(myId).bind(this)} />
}
...or you can bind it already in the constructor if you prefer:
constructor(props) {
super(props);
this._onItemPress = this._onItemPress.bind(this);
}
You did it correctly.
MyComponent is as "dumb" as it should be: it does not care about the source of its props, it acts independently from higher level of logic of the app and it can be reused somewhere else in the app.
Some improvements you can work on:
MyComponent does not need myId itself. Exclude it from the component and let parental component deals with related logics to id
Provide a safe check for props onPress. If you want to reuse MyComponent somewhere, it is better to check for existence of onPress property before calling it, or provide a default value for onPress in case developer adds in unwanted props types.
Example of MyComponent
class MyComponent extends Component {
handlePress = (e) => {
if (typeof this.props.onPress === 'function') {
this.props.onPress()
}
}
render() {
return (
<TouchableHighlight onPress={this.handlePress}>
<Text>Click me to trigger function</Text>
</TouchableHighlight>
)
}
}
and to call MyComponent in MyClass:
class MyClass extends Component {
_onItemPress(myId) {
}
render () {
return <MyComponent onPress={() => this._onItemPress(10)} />
}
}