Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
467 views
in Technique[技术] by (71.8m points)

javascript - How to render a component when state and props are changed?

I need to show the props value (which is a simple string). Each time I get new search results, I'm sending in the props.
At the very first render the props will always be undefined.

Edit:
Header.jsx

function Header() {
  const [searchString, setString] = useState('');
      
  const onChangHandler = (e) => {
    setString(e.target.value);
  };
  
  const activeSearch = () => {
    if (searchString.length > 0) {
      <Home searchResults={searchString} />;
    }
  };

  return (
    <div>
        <input
          placeholder='Search here'
          value={searchString}
          onChange={(e) => onChangHandler(e)}
        />
        <button onClick={activeSearch}>Search</button>
      </header>
    </div>
  );
}

I searched for previous stackoverflow questions and reactjs.org but found no answer.

Home.jsx

import React, { useEffect, useState } from 'react';

function Home({ searchResults }) {
  const [itemSearchResults, setResults] = useState([]);
  const [previousValue, setPreviousValue] = useState();

  // What function will re-render when the props are first defined or changed ? 
   useEffect(() => { // Doesn't work
    setResults(searchResults);
   }, [searchResults]);
           
  return (
    <div>
      <h3>Home</h3>
      <h1>{itemSearchResults}</h1>
    </div>
  );
}

export default Home;

App.js

function App() {
  return (
    <div className='App'>
      <Header />
      <Home />
      <Footer />
    </div>
  );
}

I'm sending the input string only to check if the props will change at the child component ("Home").

Any experts here know what's the problem?

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Answer

0 votes
by (71.8m points)

Why it doesn't work?

It's because the Home component is never used, even if it's included in the following snippet:

const activeSearch = () => {
  if (searchString.length > 0) {
    <Home searchResults={searchString} />;
  }
};

The activeSearch function has a couple problems:

  • it is used as an event handler though it uses JSX (outside the render phase)
  • it doesn't return the JSX (would still fail inside the render phase)

JSX should only be used within the render phase of React's lifecycle. Any event handler exists outside this phase, so any JSX it might use won't end up in the final tree.

The data dictates what to render

That said, the solution is to use the state in order to know what to render during the render phase.

function Header() {
  const [searchString, setString] = useState('');
  const [showResults, setShowResults] = useState(false);

  const onChangHandler = (e) => {
    // to avoid fetching results for every character change.
    setShowResults(false);
    setString(e.target.value);
  };

  const activeSearch = () => setShowResults(searchString.length > 0);

  return (
    <div>
        <input
          value={searchString}
          onChange={(e) => onChangHandler(e)}
        />
        <button onClick={activeSearch}>Search</button>
        {showResults && <Home query={searchString} />}
    </div>
  );
}

useEffect to trigger effects based on changing props

And then, the Home component can trigger a new search request to some service through useEffect.

function Home({ query }) {
  const [results, setResults] = useState(null);

  useEffect(() => {
    let discardResult = false;

    fetchResults(query).then((response) => !discardResult && setResults(response));

    // This returned function will run before the query changes and on unmount.
    return () => {
      // Prevents a race-condition where the results from a previous slow
      // request could override the loading state or the latest results from
      // a faster request.
      discardResult = true;

      // Reset the results state whenever the query changes.
      setResults(null);
    }
  }, [query]);

  return results ? (
    <ul>{results.map((result) => <li>{result}</li>))}</ul>
  ) : `Loading...`;
}

It's true that it's not optimal to sync some state with props through useEffect like the article highlights:

useEffect(() => {
  setInternalState(externalState);
}, [externalState]);

...but in our case, we're not syncing state, we're literally triggering an effect (fetching results), the very reason why useEffect even exists.

const { useState, useEffect } = React;

const FAKE_DELAY = 5; // seconds

function Home({ query }) {
  const [results, setResults] = useState(null);
  
  useEffect(() => {
    let queryChanged = false;
    
    console.log('Fetch search results for', query);
    
    setTimeout(() => {
      
      if (queryChanged) {
        console.log('Query changed since last fetch, results discarded for', query);
        return;
      }
      setResults(['example', 'result', 'for', query])
    }, FAKE_DELAY * 1000);
    
    return () => {
      // Prevent race-condition
      queryChanged = true;
      setResults(null);
    };
  }, [query]);
  
  return (
    <div>
      {results ? (
        <ul>
          {results.map((result) => (
            <li>{result}</li>
          ))}
        </ul>
      ) : `Loading... (${FAKE_DELAY} seconds)`}
    </div>
  );
}

function Header() {
  const [searchString, setString] = useState('');
  const [showResults, setShowResults] = useState(false);

  const onChangHandler = (e) => {
    // to avoid fetching results for every character change.
    setShowResults(false);
    setString(e.target.value);
  };

  const activeSearch = () => setShowResults(searchString.length > 0);

  return (
    <div>
        <input
          placeholder='Search here'
          value={searchString}
          onChange={(e) => onChangHandler(e)}
        />
        <button onClick={activeSearch}>Search</button>
        {showResults && <Home query={searchString} />}
    </div>
  );
}

ReactDOM.render(<Header />, document.querySelector("#app"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.13.1/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.13.1/umd/react-dom.production.min.js"></script>

<div id="app"></div>

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

2.1m questions

2.1m answers

60 comments

57.0k users

...