nobrac
nobrac

Reputation: 427

Updating an element from a button click

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

Answers (3)

Milan Adamovsky
Milan Adamovsky

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

bflemi3
bflemi3

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

Chris Sandvik
Chris Sandvik

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

Related Questions