Render between start and stop button in React Native - javascript

I wanna render between displaying a start and stop button based on pressing the buttons. That means if the start button gets pressed the stop button should be showed and vice versa. In the standard case the stop button should be rendered. With the following code I get an error because status can be read only so I need some other way to set the status.
const SampleComponent = () => {
const startButton = () => {
return <Component1 />;
};
const stopButton = () => {
return <Component2 />;
};
const status = stopButton;
return (
<View>
<Pressable
onPress={() => {
if (status == stopButton) {
status = startButton;
} else {
status = stopButton;
}
}}
>
{status}
</Pressable>
</View>
);
};
export default SampleComponent;
I need a React Native way to do this. I tried to study the docs and also other material but I just don't get it.

React way of doing this would be to use state hook.
You could use a boolean state variable and toggle it on button click.

Solution
const SampleComponent = () => {
const StartButton = () => {
return <Component1 />;
};
const StopButton = () => {
return <Component2 />;
};
const [status, setStatus] = useState(false);
return (
<View>
<Pressable
onPress={() => {
setStatus(!status);
}}
>
{status ? <StopButton /> : <StartButton />}
</Pressable>
</View>
);
};
export default SampleComponent;
Remember to give components uppercase letter.

Related

react native TouchableOpacity, different functions by first and second click

I'm creating a simple text-to-speech function for my react native app.
I have a button, when you click it for the first time, it will read the text and play the sound.
But I want to make it dynamic. For example: If you click again it should stop, if click again, should play again, etc.....
But now, it is only available for playing the sound with any click.
Where/how should I execute the stopReadText()?
I still don't have any idea about this. Thanks a lot.
Here is the code:
const readText = () => {
Speech.speak('text')
}
const stopReadText = () => {
Speech.stop()
}
return (
<View>
<TouchableOpacity onPress=(readText)>
<Divider style={styles.modalDivider} />
<Image
style={styles.speaker}
source={require('../../assets/speaker.png')}
/>
</TouchableOpacity>
</View>
)
(I am using expo-speech)
You can do it by taking on a boolean state variable:
import { useState } from 'react';
const [isPlay,setIsPlay]=useState(false)
const readText = () => {
Speech.speak('text')
}
const stopReadText = () => {
Speech.stop()
}
const handlePlay =() =>{
if(!setIsPlay){
readText()
setIsPlay(true)
}
else {
stopReadText()
setIsPlay(false)
}
}
return (
<View>
<TouchableOpacity onPress={handlePlay}>
<Divider style={styles.modalDivider} />
<Image
style={styles.speaker}
source={require('../../assets/speaker.png')}
/>
</TouchableOpacity>
</View>
)

Im not able to set an initial state in my react-native application

Im making a react-native app and I need to get some data (articles) from a GraphQL server and then, list the articles.
My problem is, when I run my app, the first time my HomeScreen component render, the state is empty and I cant see any of the articles because the response from the server takes some time to load.
I tried to use a condition where I check if the response has no errors and has finished loading, then I save the articles to the state son when I render my articleList, the state can have the articles, but it throws an error:
Too many re-renders. React limits the number of renders to prevent an infinite loop.
All I need is to have an initial state in my application before I render my home component
My code:
const HomeScreen = () => {
const {loading, error, data} = useQuery(queryRepository.GET_ARTICLES);
const [articles, setArticles] = useState([]);
const filterByCategory = name => {
setArticles(CategoryService.filterByCategoryName(name, data.items));
};
if (loading) {
return <Spinner />;
} else if (error) {
return <Error>{error}</Error>;
} else {
setArticles(data.items);
}
return (
<View style={globalStyles.container}>
<View>
<View style={globalStyles.categoryMenuContainer}>
<CategoryMenu filterByCategory={filterByCategory} />
</View>
<View style={globalStyles.reviewsContainer}>
<ArticleList articles={articles} />
</View>
</View>
</View>
);
};
Try useEffect for set state.
const HomeScreen = () => {
const { loading, error, data } = useQuery(queryRepository.GET_ARTICLES);
const [articles, setArticles] = useState([]);
const filterByCategory = name => {
setArticles(CategoryService.filterByCategoryName(name, data.items));
};
useEffect(() => {
if (!loading && !error && data) {
setArticles(data.items);
}
}, [data, loading, error]);
if (error) {
return <Error>{error}</Error>;
}
if (loading) {
return <Spinner />;
}
return (
<View style={globalStyles.container}>
<View>
<View style={globalStyles.categoryMenuContainer}>
<CategoryMenu filterByCategory={filterByCategory} />
</View>
<View style={globalStyles.reviewsContainer}>
<ArticleList articles={articles} />
</View>
</View>
</View>
)
};
Everytime you call setArticles, a new render will happen in your component, hence why you should not use setters within the render function itself, you should use hook useEffect for that

Top navigation items in header constantly flickers when typing

The image in the header constantly flickers when I type. May I ask how do I stop this flickering at the top right hand corner or accessoryRight? I am using this TopNavigation component from UI Kitten UI library. I don't think this is normal, it shouldn't happen at all. I must be doing something wrongly.
https://youtu.be/fQppQn-RzeE (How do I embed this? Editor, thank you in advance!)
The flickering happens in the title and the right side of the Navigation Header.
I made a separate component for the TopNavigation and then call it in respective screens.
Things I have tried:
Since the outcome of the Header relies on navigation props, I tried using useState and useEffect (with navProps as the dependency) to save the prop instead of reading directly from props, but to no avail
Directly adding the jsx into the TopNavigation's accessoryLeft/Right and title options
Any input is welcome, appreciate it!
TopNavigation:
const NavHeader = ({ navProps }) => {
const navigateBack = () => {
navProps.navigation.goBack();
};
const [type, setType] = React.useState('');
React.useEffect(() => {
setType(navProps.type);
}, [navProps]);
const BackIcon = props => <Icon {...props} name='arrow-back' />;
const BackAction = () => (
<TopNavigationAction icon={BackIcon} onPress={navigateBack} />
);
const renderBrand = () => (
<View style={styles.titleContainer}>
<Image source={require('../../assets/images/brand.png')} />
</View>
);
const renderLogo = () => (
<Image source={require('../../assets/images/logo.png')} />
);
return (
<TopNavigation
style={styles.topNav}
accessoryLeft={navProps.backNav && BackAction}
accessoryRight={
type === 'register' || type === 'profile' ? renderLogo : null
}
title={type === 'landing' || type === 'auth' ? renderBrand : null}
alignment='center'
/>
);
};
Import example:
<KeyboardAvoidingView
style={styles.kbContainer}
behavior={Platform.OS === 'ios' ? 'padding' : null}
>
<SafeAreaView style={styles.parentContainer}>
<NavHeader navProps={navProps} /> // Imported custom Header component here
<ScrollView>
{other content}
</ScrollView>
</SafeAreaView>
</KeyboardAvoidingView>
Perhaps requiring the images just one time and not on every render may help.
Try adding this at the top of the file (not inside a component function)
const brandImage = require('../../assets/images/brand.png');
const logoImage = require('../../assets/images/logo.png');
And then in your props instead of an inline require use the variables:
const renderBrand = () => (
<View style={styles.titleContainer}>
<Image source={brandImage} />
</View>
);
const renderLogo = () => (
<Image source={logoImage} />
);
Edit:
Since this didn't work, perhaps utilizing useMemo to memoize the components that show the images would work?
Something like
const renderBrand = useMemo(() => (
<View style={styles.titleContainer}>
<Image source={brandImage} />
</View>
),[]);
const renderLogo = useMemo(() => (
<Image source={logoImage} />
),[]);

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.

Categories