Reputation: 427
pretty new to react and was trying to figure out the best way to handle updating a custom widget from a button click. I have 3 widgets so far - a text box, button, and a twitter embedded feed. I want to basically update the twitter feed to re-render with the input in the text box after the button is clicked:
constructor(props){
super(props)
console.debug(props)
this.state = {
searchTerm: "test",
selectedOption: null
}
this.onTextChange = this.onTextChange.bind(this);
this.appChange = this.appChange.bind(this);
this.searchClicked = this.searchClicked.bind(this);
}
onTextChange(e){
this.setState({
searchTerm: e.target.value
});
};
appChange = selectedOption => {
this.setState({ selectedOption });
console.log('Option: ', selectedOption);
}
searchClicked(e){
this.setState({
searchTerm: e.target.value
})
render() {
const { searchTerm } = this.state;
return(
<div>
<div className="hashtag">
<input name="search" type="text" className="textBox" placeholder={this.state.searchTerm} onInput={this.onTextChange}/>
<br />
<button id="btnSearch" className="searchBtn" onClick={this.searchClicked}>Search</button>
<TwitterTimelineEmbed
sourceType="profile"
screenName={this.state.searchTerm}
options={{height:400, width: 400}}
/>
</div>
</div>
);
}
}
I've tried using componentDidUpate(), but haven't been successful for passing in previous props/state.
Upvotes: 0
Views: 1193
Reputation: 1579
The problem is that when you click submit you are overriding the state with e.target.value
- which is nothing since the e.target
is the actual submit button, not the input field.
You don't need to setState
inside of searchClicked
since the state is updated on each keypress (in onTextChange
).
I also removed appChange
since you don't need it in this code snippet.
The solution requires for you to add a key
to the <TwitterTimelineEmbed />
so that the old instance gets destroyed whenever you want to update the username.
It is good that the Twitter component doesn't allow you to update the component on a state change as otherwise you could easily accidentally call the Twitter API on every re-render which may (most likely) not what you want.
import React, { PureComponent } from "react";
import { TwitterTimelineEmbed } from "react-twitter-embed";
class Search extends PureComponent {
constructor(props) {
super(props);
this.timelineId = 0; // initialize key
this.state = {
searchTerm: "TwitterEng"
};
this.onTextChange = this.onTextChange.bind(this);
this.searchClicked = this.searchClicked.bind(this);
}
onTextChange(e) {
this.setState({
searchTerm: e.target.value
});
}
searchClicked(e) {
// notice we removed the setState
++this.timelineId; // this is the key for the timelinefeed
this.forceUpdate(); // you need to force update here if you want to update on search
}
render() {
const { searchTerm } = this.state;
return (
<div>
<div className="hashtag">
<input
name="search"
type="text"
className="textBox"
placeholder={searchTerm}
onInput={this.onTextChange}
/>
<br />
<button
id="btnSearch"
className="searchBtn"
onClick={this.searchClicked}
>
Search
</button>
<TwitterTimelineEmbed
key={this.timelineId}
sourceType="profile"
screenName={searchTerm}
options={{ height: 400, width: 400 }}
/>
</div>
</div>
);
}
}
export default Search;
Upvotes: 1
Reputation: 6790
Have you considered using React hooks? Here's a codepen with React 16.8.6 that solves your problem.
import React, { useState} from 'react'
import ReactDOM from 'react-dom'
const App = () => {
const [screenName, setScreenName] = useState('');
const [goSearch, setGoSearch] = useState(false);
const onChange = ({ target: { value } }) => {
if(goSearch) setGoSearch(false);
setScreenName(value);
}
const search = () => setGoSearch(true);
return (
<div className='hashtag'>
<input
name='search'
type='text'
className='textBox'
placeholder='Enter a twitter screen name'
onChange={onChange}
value={screenName}
/>
<br />
<button
id='btnSearch'
className='searchBtn '
onClick={search}
>
Search
</button>
{/* This section is just for example */}
{!!screenName && goSearch &&
<p>You searched for <b>{screenName}</b></p>
}
{/* Uncomment this section to use with TwitterTimelineEmbed */}
{/*goSearch && <TwitterTimelineEmbed
sourceType="profile"
screenName={screenName}
options={{height:400, width: 400}}
/>*/}
</div>
)
}
ReactDOM.render(
<App />,
document.getElementById('root')
);
https://codepen.io/bflemi3/pen/mZWwYw
There's more than one way to accomplish this. I chose to keep the search term and when to actually perform the search separate. Once the user clicks Search
the TwittterTimelineEmbed
is rendered with the updated screenName given as the prop.
Upvotes: 0
Reputation: 1927
So I did a couple things which should fix your problem. You were pretty close!
<input name="search" type="text" className="textBox" value={this.state.searchTerm} onChange={this.onTextChange}/>
First, I changed placeholder
to value
on your input. This will make it so that the input field is properly updated when the user types. I also changed onInput
to onChange
on this field. This will make it so that the update function actually fires.
twitterOutput: ""
Next, I added a new state value for the twitter window. If you only want this to update when the user clicks a button, you will have to store this in a separate variable. Otherwise when the user types, it will automatically be tied to the twitter window as well. If that is the behavior you wanted, you can stop here!
searchClicked(e){
this.setState({
twitterOutput: this.state.searchTerm
})
}
<TwitterTimelineEmbed
sourceType="profile"
screenName={this.state.twitterOutput}
options={{height:400, width: 400}}
/>
Finally, I changed the click function to update your new twitterOutput
state value so that it will update your timeline, as well as changed the prop on the Twitter element to reflect that.
Let me know if this helps!
Full Code:
constructor(props){
super(props)
console.debug(props)
this.state = {
searchTerm: "test",
selectedOption: null,
twitterOutput: ""
}
this.onTextChange = this.onTextChange.bind(this);
this.appChange = this.appChange.bind(this);
this.searchClicked = this.searchClicked.bind(this);
}
onTextChange(e){
this.setState({
searchTerm: e.target.value
});
};
appChange = selectedOption => {
this.setState({ selectedOption });
console.log('Option: ', selectedOption);
}
searchClicked(e){
this.setState({
twitterOutput: this.state.searchTerm
})
}
render() {
const { searchTerm } = this.state;
return(
<div>
<div className="hashtag">
<input name="search" type="text" className="textBox" value={this.state.searchTerm} onChange={this.onTextChange}/>
<br />
<button id="btnSearch" className="searchBtn" onClick={this.searchClicked}>Search</button>
<TwitterTimelineEmbed
sourceType="profile"
screenName={this.state.twitterOutput}
options={{height:400, width: 400}}
/>
</div>
</div>
);
}
}
Upvotes: 0