Stateless function components cannot hav refs - reactjs

I'm building search page similar to Facebook or instagram. Basically if we press search button, it navigates to 'SearchScreen'. When its component is mounted, I want to set the search header is focused (cursor).
My problem is when I set TextInput ref as a prop. And I'm getting Stateless function components cannot have refs error. Is this right approach? Why is it not working? Do you know any better approach other than this?
I added _renderHeader private function to renderHeader props in FlatList.
This is _renderHeader
_renderHeader = () => {
return (
<View style={styles.layoutheader}>
<View style={styles.containerheader}>
<RkTextInput
rkType='row'
ref="sbar" /////////////////////HERE////////////
autoCapitalize='none'
autoCorrect={false}
label={<RkText rkType='awesome' style={{color:'white'}}>{FontAwesome.search}</RkText>}
placeholder='Search'
underlineWidth="1"
underlineColor="white"
style={styles.searchBarheader}
inputStyle={{color:'white'}}
labelStyle={{marginRight:0}}
value={this.state.inputText}
onChangeText={(inputText)=>{this.setState({inputText})}}
/>
<View style={styles.left}>
<RkButton
rkType='clear'
style={styles.menuheader}
onPress={() => {
this.props.navigation.goBack()
}}>
<RkText style={styles.titleText} rkType='awesome hero'>{FontAwesome.chevronLeft}</RkText>
</RkButton>
</View>
</View>
</View>
)
}
componentDidMount() {
this.refs.sbar.focus(); ////////// Here I want to focus RkTextInput when it's loaded
}
UPDATE here is actual code as requested
class SearchScreen extends Component {
static navigationOptions = ({navigation}) => ({
header: null
})
state = {
active: false,
inputText: ''
}
...
_renderRow = (row) => {
...
);
}
_renderHeader = () => {
...
}
render() {
return (
<FlatList
data={null}
renderItem={this._renderRow}
renderHeader={this._renderHeader}
keyExtractor={this._keyExtractor}
ListHeaderComponent={this._renderHeader}
/>
);
}
componentDidMount() {
this.refs.sbar.focus();
}
}

What seems to me is that you are not using the refs the right way. The way you are using them has been deprecated. You should follow this syntax:
<input
type="text"
ref={(input) => { this.textInput = input; }}
/>
and when you want to access it you can do this.textInput. In your case, this.textInput.focus().

You're using RkTextInput which is a functional component and it cannot have a ref. That's why you can't focus it.
I don't see a way to focus the input other than wrapping the Component, getting the ref of the root and finding your input element in order to focus it. A rough example :
class RoughExample extends React.Component {
componentDidMount() {
//find the input from your root
this.input = this.root.querySelector('input');
//if it exists, focus
this.input && this.input.focus();
}
render() {
<div ref={ (node) => {this.root = node;} }>
<RkTextInput />
</div>
}
}

Related

How to update back prop to child componet using react hook

I have a parent componet like this, just to show the dialog
The Child Component ( Main to show dialog)
export const MedicalRecord = memo(function MedicalRecord() {
// const onPressViewAll = useCallback(() => {}, [])
const [show, setShow] = useState(false) ///to show dialog
function hanndleDialog() {
setShow(!show) set to show dialog
}
// useEffect(() => {
// if (show == true) {
// setShow(!show)
// }
// },[show])
return (
<SummaryViewContainer
count={5}
title={"dashboardScreen.medicalRecords.title"}
onPress={() => {
hanndleDialog()
}}
>
<View>
{show && (
<ProgressDialog
show={show} //pass t
callback={() => {
hanndleDialog()
}}
/>
)}
<RecordItem />
<RecordItem />
<RecordItem />
</View>
</SummaryViewContainer>
)
})
And parent componet to show this dialog
export default function DialogTesting(show: boolean, { callback }) {
const [showDialog, doShow] = useState(show) //show to present show in child
return (
<View>
{/* <Button
title="click"
onPress={() => {
setShow(true)
}}
>
<Text>Show dialog</Text>
</Button> */}
<Dialog
visible={showDialog}
title="Record New Progress"
style={DIALOG}
onClose={() => {
doShow(false)
callback()
}}
>
But i cant figure out how to open dialog again when close the dialog, it only open for once, i try React Hook : Send data from child to parent component but not work !
How can i show dialog and when i click close button, the children return orignal state so i can click it again, thank you guy so much
Here is a short video of this problem
https://recordit.co/0yOaiwCJvL
I am assuming that you want to find a way to show hide a component based on click. So this is the sandbox for the same.
In this solution, instead of using a derived state, the state is held in the parent's state and the child is mounted/unmounted based on that state.
The state can be updated by a method present in the parent and this method is passed to the child to be triggered on the "hide child" button. The same method is used to show the child component as well.
Below is the core code for the same,
import React from "react";
const Dialog = ({ hideMe }) => {
return (
<div>
<div>I am dialog</div>
<button onClick={hideMe}>Hide me</button>
</div>
);
};
class App extends React.Component {
constructor(props) {
super(props);
this.state = { showDialog: false };
}
toggleDialog = () => {
this.setState((prevState) => {
return { showDialog: !prevState.showDialog };
});
};
render() {
return (
<div>
<div>I am parent.</div>
<button onClick={this.toggleDialog}>Toggle Dialog</button>
{this.state.showDialog ? <Dialog hideMe={this.toggleDialog} /> : null}
</div>
);
}
}
export default App;

React native render component based on TextInput onfocus

I want to display something in my react component when user clicks into a text input (something similar to Instagram's search, where if you click on their input field, a search component suggestion shows up.
const SearchScreen = props => {
const renderSearch = () => {
return (
<>
// need to display the search suggestion
</>
)
}
return (
<>
<TextInput
placeholder="Search"
onChangeText={text => handleChange(text)}
value={searchText}
onFocus={() => renderSearch()} // based on focus, then render component
/>
<View>
// how do I render here?
// this will render on load, but need to render onFocus
{renderSearch}
</View>
</>
);
};
You can apply a similar pattern than stackoverflow.com/a/34091564/1839692.
For instance you can try something like :
const SearchScreen = props => {
const [searchFocus, setSearchFocus] = useState(false)
const renderSearch = () => {
return (
<>
// need to display the search suggestion
</>
)
}
return (
<>
<TextInput
placeholder="Search"
onChangeText={text => handleChange(text)}
value={searchText}
onFocus={() => setSearchFocus(true)}
onBlur={() => setSearchFocus(false)}
/>
{ searchFocus
? <View>
{renderSearch}
</View>
: <View>
// Add here the code to display when not searching
</View>
}
</>
);
};

How to clear TextInput on send button in React native with redux

I am working on chat app using react native with redux, where messages are sending through the send button. But whenever I sent a message on hitting the send button, the TextInput is not clearing.
I want to clear the TextInput field on hitting the send button. Here I am working in redux so I don't want to use state with value.
Here is the code :
class Chat extends Component {
componentWillMount() {
this.props.fetchChat(this.props.receiverId);
}
message(text) {
this.props.writeMsg(text);
}
onSend = () => {
const { userId , receiverId, text } = this.props;
this.props.sendMessage({userId , receiverId, text});
}
render() {
return (
<View style={{ flex: 1 }}>
<FlatList
inverted={-1}
style={styles.list}
extraData={this.props}
data={this.props.convo}
keyExtractor = {(item) => {
return item.id;
}}
renderItem=
<ChatItem value={this.renderItem} />
/>
<MessageInput
onChangeText={text => this.message(text)}
onPress={this.onSend }
/>
</View>
);
}
}
And this is the component MessageInput's code:
<View style={inputContainer}>
<TextInput style={inputs}
placeholder="Write a message..."
onChangeText={onChangeText}
/>
</View>
<TouchableOpacity style={btnSend} onPress={onPress }>
<Icon
name='send'
type='MaterialIcons'
color='#fff'
style={iconSend}
/>
</TouchableOpacity>
You can use a ref to clear the value from Chat.
Add a new ref inside your constructor
constructor(props) {
super(props);
this.textInput = React.createRef();
}
Pass the ref into MessageInput.
render() {
...
<MessageInput
onChangeText={text => this.message(text)}
onPress={this.onSend }
ref={this.textInput}
/>
...
}
Modify MessageInput (I am going to assume it's a functional component)
const MessageInput = (props, ref) => (
...
<TextInput style={inputs}
placeholder="Write a message..."
onChangeText={onChangeText}
ref={ref}
/>
...
)
Finally, switch back to the Chat component and update onSend
onSend = () => {
const { userId , receiverId, text } = this.props;
this.props.sendMessage({userId , receiverId, text});
this.textInput.current.clear(); // Clear the text input
}
You can try with clearing the text property after the message is sended, (if the text property is what is rendered in the TextInput):
onSend = () => {
const { userId , receiverId, text } = this.props;
this.props.sendMessage({userId , receiverId, text});
this.message('');
}
or
onSend = () => {
const { userId , receiverId, text } = this.props;
this.props.sendMessage({userId , receiverId, text});
this.props.writeMsg('');
}

Sending data from Child to Parent React

I have subdivided my components and I want to change state of text using deleteName function from child component. However I have used onPress={this.props.delete(i)} in my child component which is not working. The error that occurs for me is:
undefined variable "I"
Here is my code:
App.js
export default class App extends Component {
state = {
placeName: '',
text: [],
}
changeName = (value) => {
this.setState({
placeName: value
})
}
deleteName = (index) => {
this.setState(prevState => {
return {
text: prevState.text.filter((place, i) => {
return i!== index
})
}
}
}
addText = () => {
if (this.state.placeName.trim === "") {
return;
} else {
this.setState(prevState => {
return {
text: prevState.text.concat(prevState.placeName)
};
})
}
}
render() {
return (
<View style={styles.container}>
<View style={styles.inputContainer}>
<Input changeName={this.changeName}
value={this.state.placeName} />
<Button title="Send" style={styles.inputButton}
onPress={this.addText} />
</View>
<ListItems text={this.state.text} delete={this.deleteName}/>
{/* <View style={styles.listContainer}>{Display}</View> */}
</View>
);
}
}
and child component ListItems.js
const ListItems = (props) => (
<View style={styles.listitems}>
<Text>{this.props.text.map((placeOutput, i) => {
return (
<TouchableWithoutFeedback
key={i}
onPress={this.props.delete(i)}>
onPress={this.props.delete}
<ListItems placeName={placeOutput}/>
</TouchableWithoutFeedback>
)
})}
</Text>
</View>
);
You need to bind the index value at the point of passing the props to the child.
delete = index => ev => {
// Delete logic here
}
And in the render function, you can pass it as
items.map((item, index) => {
<ChildComponent key={index} delete={this.delete(index)} />
})
In your child component, you can use this prop as
<button onClick={this.props.delete}>Click me</button>
I have created a Sandbox link for your reference
Instead of onPress={this.props.delete(i)}, use onPress={() => this.props.delete(i)}
In order to have the cleaner code, you can use a renderContent and map with }, this);like below. Also you need to use: ()=>this.props.delete(i) instead of this.props.delete(i) for your onPress.
renderContent=(that)=>{
return props.text.map((placeOutput ,i) => {
return (
<TouchableWithoutFeedback key={i} onPress={()=>this.props.delete(i)}>
onPress={this.props.delete}
</TouchableWithoutFeedback>
);
}, this);
}
}
Then inside your render in JSX use the following code to call it:
{this.renderContent(this)}
Done! I hope I could help :)

How to record other events in FlatList?

I am using FlatList to render items. Each item is a separate card style component. Each item has onPress event handler which changes the component.
Here is my Flatlist.
<FlatList
data={data}
renderItem={({ item }) => {
return <CardItem courseData={item} />
}}
ref={this.flatList}
keyExtractor={
(item) => { return item.content_address }
}
initialNumToRender={10}
showsVerticalScrollIndicator={false}
style={{ marginTop: 50 }}
/>
Here is the CardItem Component
constructor{
this.state = {change:false}
}
_onPress = () => {
this.setState({change: true})
}
render() {
if (this.state.change) {
return (//return changes)
} else {
return (
<TouchableOpacity
ref="myRef"
activeOpacity={0.5}
onPress={this._onPress}>
...
</TouchableOpacity>
)
}
}
Now what I want is to have only one card component changed at a time.
So when a user touches on 1st card component, it should change. But when a user touches 2nd card component, 1st should change back to the previous state and 2nd should change.
I saw FlatList documentation here but not sure which methods can help me?
If you store your toggled item in parent state you can check and render accordingly. Also storing toggled value in child state will cause a bug where if the item moves enough off to the screen it will be unmounted and the internal state of the child component will be reset. This would cause undesired toggle in your list. Storing state in parent component will help to overcome this issue.
Example
class App extends Component {
constructor() {
this.state = { toggledItem: null }
}
onPressItem = (itemId) => {
this.setState({toggledItem: itemId})
}
render() {
<FlatList
data={data}
renderItem={({ item }) => {
return <CardItem
courseData={item}
onPress={this.onPressItem}
toggeled={item.id === this.state.toggledItem}
/>
}}
ref={this.flatList}
keyExtractor={
(item) => { return item.content_address }
}
initialNumToRender={10}
showsVerticalScrollIndicator={false}
style={{ marginTop: 50 }}
/>
}
}
class CardItem extends Component {
_onPress = () => {
this.props.onPress(this.props.courseData.id)
}
render() {
if (this.props.toggeled) {
return (//return changes)
} else {
return (
<TouchableOpacity
ref="myRef"
activeOpacity={0.5}
onPress={this._onPress}>
...
</TouchableOpacity>
)
}
}
}

Resources