How do I refresh a react component by clicking on a button outside the component?

Viewed 1492

I'm new to web development and decided to learn ReactJs. I am trying to create a random quote generator as a part of freecodecamp's frontend libraries project. Problem: I have a react component that fetches the quotes from a website and then displays a random quote on the screen. I also have a button outside of the component whose sole purpose is to get a new code and display it onscreen. How do I refresh the react component whenever I click the button? Any help is appreciated!:D Here's my component:

class MyComponent extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      quote: ''
    };
  }

  componentDidMount() {
    fetch("https://type.fit/api/quotes")
      .then(res => res.json())
      .then(
        (result) => {
          this.setState({
            quote: result[Math.floor(Math.random() * result.length)]
          });
        },
      )
  }


  render() {
    return ( 
      <p id = 'reactQuote'>{this.state.quote.text}< /p>
    );
  }
}

ReactDOM.render( MyComponent />, document.getElementById('text'))
<div id="text"></div>
<button id='new-quote'>New Quote</button>

3 Answers

create a component that would incapsulate both the button and your component, have the quote as state in the upper component and pass it down as props to your component, now when you press the button it will change the state in the upper component and it will be passed down to your component forcing it to refresh and display the new quote.

You can call a function on your button whenever you click on it. That function can be passed down to your button component as a prop.

function fetchData(){
     fetch("https://type.fit/api/quotes")
        .then(res => res.json())
        .then(
          (result) => {
            this.setState({
              quote: result[Math.floor(Math.random() * result.length)]
          });
        },
      )
}

<button id='new-quote' onClick={fetchData}>New Quote</button>

The component will automatically refresh when its state changes but the button and the text should be part of the component itself (or children of the component). An example (using a single component) would be:

// your component
class RandomQuoteMachine extends React.Component {
  constructor() {
    super();
    this.state = {
      quotes: [], // here you store an array of quote objects
      currentQuote: null // here you store the quote object that is currently displayed
    };
    this.getRandomQuote = this.getRandomQuote.bind(this)
  }

  // when the component mounts, fetch the quotes from the API
  componentDidMount() {
    fetch("https://type.fit/api/quotes") // fetch the entire list of quotes from the API
      .then(response => response.json()) // turn the response into a Javascript object
      .then(result => {
        // update the state
        this.setState({
          quotes: result,
          currentQuote: result[Math.floor(Math.random() * result.length)] || null
        });
      });
  }

  getRandomQuote() {
    // randomly select a quote from the state
    const newQuote = this.state.quotes[Math.floor(Math.random() * this.state.quotes.length)] || null;
    // update the state with the new current quote
    this.setState({
      currentQuote: newQuote
    });
  }

  render() {
    return (
      <div>
        <blockquote>{ this.state.currentQuote && this.state.currentQuote.text || '' }</blockquote>
        <p className = "author">{ this.state.currentQuote && this.state.currentQuote.author || 'unknown' }</p>
        <button onClick = { () => this.getRandomQuote() }>get a new quote from the list</button>
      </div>
    );
  }
}

// mounting the component in the page
ReactDOM.render( < RandomQuoteMachine / > , document.getElementById('app'))
/* A bit of styling, just for presentation sake */

* {
  font-family: sans-serif;
}

blockquote {
  font-size: 2rem;
  font-style: italic;
  margin: 0;
  padding: 0.5rem 0.5rem 0.5rem 3rem;
  border-left: 3px solid #ccc;
}

.author {
  font-size: 1.2rem;
  padding-right: 0.5rem;
  text-align: right;
}

.author::before {
  content: "(";
}

.author::after {
  content: ")";
}
<!-- Importing React and ReactDOM -->
<script crossorigin src="https://unpkg.com/react@17/umd/react.production.min.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@17/umd/react-dom.production.min.js"></script>

<!-- This is where your component will be mounted -->
<div id="app">

</div>

Or, if you prefer functional components and hooks:

const RandomQuoteMachine = () => {
  const [quotes, setQuotes] = React.useState([]);
  const [currentQuote, setCurrentQuote] = React.useState(null);

  React.useEffect(() => {
    fetch("https://type.fit/api/quotes")
      .then(response => response.json())
      .then(result => {
        setQuotes(result);
        setCurrentQuote(result[Math.floor(Math.random() * result.length)] || null);
      })
  }, [setQuotes, setCurrentQuote]);

  const getRandomQuote = React.useCallback(() => {
    const newQuote = quotes[Math.floor(Math.random() * quotes.length)] || null;
    setCurrentQuote(newQuote)
  }, [quotes, setCurrentQuote]);

  return (
    <div>
      <blockquote> { currentQuote && currentQuote.text || '' } </blockquote>
      <p className = "author"> { currentQuote && currentQuote.author || 'unknown' }</p>
      <button onClick = { () => getRandomQuote() }> get a new quote from the list </button>
    </div>
  );
}

ReactDOM.render(<RandomQuoteMachine />, document.getElementById('app'));
* {
  font-family: sans-serif;
}

blockquote {
  font-size: 2rem;
  font-style: italic;
}

.author {
  font-size: 1rem;
  text-align: right;
}

.author::before {
  content: "(";
}

.author::after {
  content: ")";
}
<script crossorigin src="https://unpkg.com/react@17/umd/react.production.min.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@17/umd/react-dom.production.min.js"></script>

<div id="app">

</div>

Related