How to use prop spreading and extracting props at the same time - javascript

I have two components like this
const TextLarge = (props) => { // props are classes,color and children
return <Text
classes={'text-large-style'}
{...props}>
{props.children}
</Text>
}
const Text = ({
children,
color,
classes,
}) => {
return <p
className={clsx('text-styles', classes)}
style={{color: color}}>
{children}
</p>
}
Now, when I pass a class to <TextLarge />
<TextLarge
classes='some-special-style'>
Foo
<TextLarge>
This overwrites classes='text-large-style' because {...props} spreads classes='some-special-style into <TextLarge/> after classes='text-large-style.
Is there a (React/elegant) way to take classes out of props, so something like
const TextLarge = (props) => { // props are classes,color and children
return <Text
classes={clsx('text-large-style', props.classes)}
{...props}> // no classes in here
{props.children}
</Text>
}
yet still spreading the rest of the {...props} into <Text />?
I know that could change the order, making classes={clsx('text-large-style', props.classes)} overwrite {...props}
const TextLarge = (props) => { // props are classes,color and children
return <Text
{...props}
classes={clsx('text-large-style', props.classes)}>
{props.children}
</Text>
}
but I want more control.

Is there a (React/elegant) way to take classes out of props
Yes, you can use destructuring with a rest target:
const TextLarge = ({classes, ...rest}) => {
// ...
};
Actually, you probably want to remove children as well:
const TextLarge = ({classes, children, ...rest}) => {
// ...
};
Then ...rest won't include classes (or children), and you can include classes in your explicit prop (if you want to pass them on):
const TextLarge = ({classes, children, ...rest}) => {
return (
<Text {...rest} classes={`text-large-style ${classes ?? ""}`}>
{children}
</Text>
);
};

Related

How to pass onPress to props.children?

I'm trying to make a wrapper component in react-native that I can pass down all its props to the children it wraps around. What I really want is to pass down all function props down to all its children. It looks something like this below. I want the onPress in Wrapper to be called when the TouchableOpacity is pressed.
I tried this below but it doesn't work
const Wrapper = ({children,...props})=>{
return <View {...props}>{children}</View>
}
const App = ()=>{
return (
<View style={{flex:1}}>
<Wrapper onPress={()=>{console.log(2)}}>
<TouchableOpacity/>
</Wrapper>
</View>
)
}
It looks like you're looking to map the children and apply the props to each one. That might look like this:
const Wrapper = ({children,...props})=>{
return (<>
{React.Children.map(children, child => (
React.cloneElement(child, {...props})
))}
</>);
}
(method of mapping the children borrowed from this answer)
const App = () => {
return (
<View style={{ flex: 1 }}>
<TouchableOpacity onPress={() => {
// do the action need here here
}}>
<Wrapper >
</Wrapper>
</TouchableOpacity>
</View>
)
}
I would advise you to use hooks function instead
If you try to reuse functions that are related
** useAdd.js **
export default () => {
const addFuction(a, b) => {
// do preprocessing here
return a + b
}
return [addFuction]
}
main componet
import useAdd from "/useAdd";
const App = () => {
const [addFuction] = useAdd()
return (
<View style={{ flex: 1 }}>
<TouchableOpacity onPress={() => {
addFuction(4,5)
}}>
...action component...
</TouchableOpacity>
</View>
)
}
console in useAdd hook.... to see visual changes use the react useState

Invalid Hook Call - React Hooks

I'm really new to JS and React. I get this error:
Invalid Hook Call
when I try to make a component appear and disappear when another component is clicked. This is my code:
const RenderList = ({data}) => {
return data.map((option, index) => {
return <Item title={option}/>
});
};
const Header = ({ title, style, press }) => (
<TouchableHighlight onPress={press}>
<Text style={style} >{title}</Text>
</TouchableHighlight>
)
const RenderItem = ( {item} ) => {
console.log(styles)
let dataToShow;
const [listState, setListState] = useState(true);
if (listState){
dataToShow = <RenderList data={item.data}/>
} else {
dataToShow = <Text/>
}
return (
<View style={styles.section}>
<Header title={item.title} style={styles.header} press={setListState(!listState)}/>
{dataToShow}
</View>
)}
EDIT
RenderItem is used in a flat list element as a function. (From what I understand)
const SettingsSection = (props) => {
const db = props.data;
return(
<View>
<FlatList
style={styles.sectionList}
data={db}
renderItem={RenderItem}
keyExtractor={item=>item.title}
ItemSeparatorComponent={FlatListItemSeparator}
/>
</View>
);
}
renderItem, as the name suggests, is a render prop, and as such is called directly (like so: renderItem({item})), not instantiated as a component (like so: <RenderItem item={item}/>).
This translates to React not creating the appropriate rendering "context" for hooks to work. You can make sure your RenderItem function is instantiated as a component by using it like this on the render prop:
<FlatList
style={styles.sectionList}
data={db}
renderItem={item => <RenderItem {...item}/>} // see here!
keyExtractor={item=>item.title}
ItemSeparatorComponent={FlatListItemSeparator}
/>
That way, RenderItem is treated as a component and thus can use hooks.
I think problem is occurring due to setListState(!listState) with press. I suggest you to wrap your state changing method into a function. Because onPress accepts only function type but you are giving it a return statement from hooks.
const RenderList = ({data}) => {
return data.map((option, index) => {
return <Item title={option}/>
});
};
const Header = ({ title, style, press }) => (
<TouchableHighlight onPress={press}>
<Text style={style} >{title}</Text>
</TouchableHighlight>
)
const RenderItem = ( {item} ) => {
console.log(styles)
let dataToShow;
const [listState, setListState] = useState(true);
if (listState){
dataToShow = <RenderList data={item.data}/>
} else {
dataToShow = <Text/>
}
return (
<View style={styles.section}>
<Header
title={item.title}
style={styles.header}
press={()=>{
setListState(!listState)
}}
/>
{dataToShow}
</View>
)}

React native change state loop

I'm building a react native app where a post has comments. I only want to
show the comments when the user clicks on load comments.... The problem
is how do I handle the state for each post (there are multiple posts). I tried
this but it's not working (renderPost is a loop):
const renderPost = ({ item, index}) => {
let fetchComments = false;
return (
<View style={[t.mB6]}>
<View style={[t.roundedLg, t.overflowHidden, t.shadow, t.bgWhite, t.hAuto]}>
<TouchableOpacity
key={item.id}
onPress={() => {
fetchComments = true;
}}>
<Text style={[t.fontBold, t.textBlack, t.mT2, t.mL4, t.w1_2]}>
load comments...
</Text>
</TouchableOpacity>
</View>
{ fetchComments ? <Comments postId={item.id}/> : null }
</View>
)
}
In the code above I set let fetchComments to true when the user clicks on load comments....
renderPost is a functional component that doesn't have its own render and its own state, you may resolve this passing a function that changes state through renderPost props in its Father React.Component.
Example:
//imports
class FatherComponentWithState extends React.component{
state={
fetchComments:false,
//(...OTHERSTUFFS)
}
setFetchComments = () =>{
this.setState({fetchComments:true})
}
render(){
return(
//(...FatherComponentStuffs)
{new renderPost({
setFetchComments: this.setFetchComments,
fetchComments:this.state.fetchComments,
//(...renderPostOtherStuffs like item, index)
})}
//(...FatherComponentStuffs)
)}}
The renderPost function will receive it with something like this:
const renderPost = (props) =>{
let fetchComments = props.fetchComments;
let setFetchComments = props.setFetchComments;
let item = props.item
let index = props.index
//...renderPost return remains the same
}
P.S.: If you have multiple renderPosts, you can use fetchComments as an array of booleans and set the state to true passing an index as parameter of the setFetchComments function.

is there way to check and unchecked the "check-boxes" from my example?

this is my example that I try to check and unchecked the "check-boxes" but I get confused and i will be happy if someone shows me how it should be done.
import React, { useState } from 'react';
import { View, Text, StyleSheet } from 'react-native';
import { CheckBox } from 'react-native-elements';
const NewPlaceScreen = props => {
const [checked, setChecked] = useState(false);
return (
<View>
<CheckBox
iconRight
right
title="apple"
checked={checked}
onPress={() => setChecked(true)}
/>
<CheckBox
iconRight
right
title="kiwi"
checked={checked}
onPress={() => setChecked(true)}
/>
</View>
);
};
NewPlaceScreen.navigationOptions = {
headerTitle: 'viewsqq'
};
const styles = StyleSheet.create({
TextStyle: {
fontWeight: 'bold',
color: 'grey'
}
});
export default NewPlaceScreen
thats my example above
You need to set them to the opposite of their previous state when pressed. You can do this by using the setState callback:
onPress={() => setChecked(prev => !prev)}
At the moment your check boxes are both using the same state variable checked so they will stay in sync - changing one will change the other. If this is not what you want, you should create a separate state variable for each checkbox.
UPDATE:
To treat each checkbox independently, you need to create state for each checkbox:
const [isAppleChecked, setIsAppleChecked] = useState(false)
const [isKiwiChecked, setIsKiwiChecked] = useState(false)
return (
<View>
<CheckBox
iconRight
right
title="apple"
checked={isAppleChecked}
onPress={() => setIsAppleChecked(prev => !prev)}
/>
<CheckBox
iconRight
right
title="kiwi"
checked={isKiwiChecked}
onPress={() => setIsKiwiChecked(prev => !prev)}
/>
</View>
)
You need to have a separate state for each box, otherwise they will always show the same thing. And you need to set the new state to the opposite of the old state:
const NewPlaceScreen = props => {
const [appleChecked, setAppleChecked] = useState(false);
const [kiwiChecked, setKiwiChecked] = useState(false);
return (
<View>
<CheckBox
iconRight
right
title='apple'
checked={appleChecked} // use the apple-specific state
onPress={() => setAppleChecked(prevState => !prevState)} // use the new apple state function
/>
<CheckBox
iconRight
right
title='kiwi'
checked={kiwiChecked} // use the new kiwi state
onPress={() => setKiwiChecked(prevState => !prevState)} // use the new kiwi function
/>
</View>
);
};

Destructuring and passing in full object simultaenously

I have a simple React component:
const FullContainer = ({
backgroundColor,
children,
}) => (
<Container
backgroundColor={backgroundColor}
>
{children}
</Container>
);
I'm currently destructing the only two properties I expect my component to use, but I'd also like to pass in props and spread it in as well:
const FullContainer = (props, {
backgroundColor,
children,
}) => (
<Container
backgroundColor={backgroundColor}
{...props}
>
{children}
</Container>
);
Oddly enough, this breaks my page with no errors. I must be doing something wrong. Is my syntax wrong?
You can make use of rest spread syntax that provides the remaining properties which aren't destructured as an array like
const FullContainer = ({
backgroundColor,
children,
...props
}) => (
<Container
backgroundColor={backgroundColor}
{...props}
>
{children}
</Container>
);

Categories