简体   繁体   中英

React js: can I pass data from a component to another component?

I'm new to React and I'm still learning it. I'm doing a personal project with it.

Let me explain my problem:

I have a component called <NewReleases /> where I make an ajax call and take some datas about some movies out on cinemas today. (I take title, poster img, overview etc...) I put all the data in <NewReleases /> state, so that state becomes an object containing an object for each movie and each object contains title poperty, poster property etc... Then I render the component so that it looks like a grid made by movies posters, infos and so on. And this works well.

Then I need a component <Movie /> to take some datas from the state of <NewReleases /> and render them on the HTML. I read other questions where people were having a similar problem, but it was different because they had a children component that was rendered by the parent component. And in that way, people suggested to pass state as props. I can't do that because my <Movie /> component is not rendered by <NewReleases /> . <NewReleases /> makes the ajax call and only renders a JSX grid based on the retrieved data.

On index.js I have setup the main page this way:

import React from 'react';
import ReactDOM from 'react-dom';
import {BrowserRouter, Switch, Route} from 'react-router-dom';

import {Home} from './home';
import {Movie} from './movie';
import './css/index.css';

class App extends React.Component {
  render() {
    return(
      <BrowserRouter>
        <Switch>
          <Route path={'/movie/:movieTitle'} component={Movie} />
          <Route path={'/'} component={Home} />
        </Switch>
      </BrowserRouter>
    );
  }
}

ReactDOM.render(
  <App />,
  document.getElementById('root')
);

(You can't see <NewReleases /> here because it is rendered inside of <Home /> component, which also renders a header and a footer.)

So when I click on a movie rendered by <NewReleases /> , the app will let me go on localhost:3000/movie/:movieTitle where :movieTitle is a dynamic way to say the title of the movie (so for example if I click the poster of Star Wars rendered by <NewReleases /> , I will go on localhost:3000/movie/StarWars ). On that page I want to show detailed infos about that movie. The info are stored in <NewReleases /> state but I can't have access to that state from <Movie /> (I guess).

I hope you got what I want to achieve. I don't know if it is possible. I had an idea: on the <Movie /> I could do another ajax call just for the movie that I want but I think it would be slower and also I don't think it would be a good solution with React.

Note that I'm not using Redux, Flux etc... only React. I want to understand React well before to move to other technologies.

The way you wanna do is more complicated. With parents componentes that's easy to do. And with Redux is much more easy.

But, you wanna this way. I think if you have a state in the app, pass to home a props to set a movie-state and pass this movie-state to component Move, works fine.

The problem is that Route does't pass props. So there is a extensible route you can do. In the code below I get from web this PropsRoute.

import React from 'react';
import ReactDOM from 'react-dom';
import {BrowserRouter, Switch, Route} from 'react-router-dom';

import {Home} from './home';
import {Movie} from './movie';
import './css/index.css';

    class App extends React.Component {
      constructor() {
        super();

        this.state = {
          movie: {}
        }

        this.setMovie = this.setMovie.bind(this);
      }

      setMovie(newMovie) {
        this.setState({
          movie: newMovie
        });
      }

      render() {
        return(
          <BrowserRouter>
            <Switch>
              <PropsRoute path={'/movie/:movieTitle'} movie={this.state.movie} component={Movie} />
              <PropsRoute path={'/'} component={Home} setMovie={this.setMovie} />
            </Switch>
          </BrowserRouter>
        );
      }
    }

    ReactDOM.render(
      <App />,
      document.getElementById('root')
    );


-----
const renderMergedProps = (component, ...rest) => {
  const finalProps = Object.assign({}, ...rest);
  return (
    React.createElement(component, finalProps)
  );
}

const PropsRoute = ({ component, ...rest }) => {
  return (
    <Route {...rest} render={routeProps => {
      return renderMergedProps(component, routeProps, rest);
    }}/>
  );
}

I thinks this can solve your problem.

Here's a quick example. Store your state in a parent component and pass down the state down to your components. This example uses React Router 4, but it shows how you can pass down the setMovie function and movie information via state to one of your child components. https://codepen.io/w7sang/pen/owVrxW?editors=1111

Of course, you'll have to rework this to match your application, but a basic run down would be that your home component should be where you're grabbing your movie information (via AJAX or WS) and then the set function will allow you to store whatever information you need into the parent component which will ultimately allow any child components to access the information you have stored.

 const { BrowserRouter, Link, Route, Switch } = ReactRouterDOM; const Router = BrowserRouter; // App class App extends React.Component{ constructor(props) { super(props); this.state = { movie: { title: null, rating: null } }; this.setMovie = this.setMovie.bind(this); } setMovie(payload) { this.setState({ movie: { title: payload.title, rating: payload.rating } }); } render(){ return( <Router> <div className="container"> <Layout> <Switch> <Route path="/select-movie" component={ () => <Home set={this.setMovie} movie={this.state.movie} />} /> <Route path="/movie-info" component={()=><MovieInfo movie={this.state.movie}/>} /> </Switch> </Layout> </div> </Router> ) } } //Layout const Layout = ({children}) => ( <div> <header> <h1>Movie App</h1> </header> <nav> <Link to="/select-movie">Select Movie</Link> <Link to="/movie-info">Movie Info</Link> </nav> <section> {children} </section> </div> ) //Home Component const Home = ({set, movie}) => ( <div> <Movie title="Star Wars VIII: The Last Jedi (2017)" rating={5} set={set} selected={movie} /> <Movie title="Star Wars VII: The Force Awakens (2015)" rating={5} set={set} selected={movie} /> </div> ) //Movie Component for displaying movies //User can select the movie const Movie = ({title, rating, set, selected}) => { const selectMovie = () => { set({ title: title, rating: rating }); } return ( <div className={selected.title === title ? 'active' : ''}> <h1>{title}</h1> <div> {Array(rating).fill(1).map(() => <span>★</span> )} </div> <button onClick={selectMovie}>Select</button> </div> ) } //Movie Info //You must select a movie before movie information is shown const MovieInfo = ({movie}) => { const { title, rating } = movie; //No Movie is selected if ( movie.title === null ) { return <div>Please Select a Movie</div> } //Movie has been selected return ( <div> <h1>Selected Movie</h1> {title} {Array(rating).fill(1).map(() => <span>★</span> )} </div> ) } ReactDOM.render(<App />,document.getElementById('app')); 
 nav { margin: 20px 0; } a { border: 1px solid black; margin-right: 10px; padding: 10px; } .active { background: rgba(0,0,0,0.2); } 
 <div id="app"></div> 

Create a manual object store to get/set the movie information and use it. That's it. Try the following code. That should answer all your questions. Click on any of the new releases, it will redirect to movie info screen with all the details. If you feel bad about the new releases data always refreshing, you may have to create another store, then get/set the data by checking the data exist in store.

Note: Using store and using title (duplicates may occur) in browser URL makes some problems when user refreshes the browser. For that, use id in browser URL, fetch the details using AJAX call and set that details in store.

 //store for movie info const movieInfoStore = { data: null, set: function(data) { this.data = data; }, clear: function() { this.data = null; } }; class MovieInfo extends React.Component { componentWillUnmount() { movieInfoStore.clear(); } render() { return ( <div> <pre> {movieInfoStore.data && JSON.stringify(movieInfoStore.data)} </pre> <button onClick={() => this.props.history.goBack()}>Go Back</button> </div> ) } } MovieInfo = ReactRouterDOM.withRouter(MovieInfo); class NewReleases extends React.Component { handleNewReleaseClick(newRelease) { movieInfoStore.set(newRelease); this.props.history.push(`/movie/${newRelease.title}`); } render() { const { data, loading } = this.props; if(loading) return <b>Loading...</b>; if(!data || data.length === 0) return null; return ( <ul> { data.map(newRelease => { return ( <li onClick={() => this.handleNewReleaseClick(newRelease)}>{newRelease.title}</li> ) }) } </ul> ) } } NewReleases = ReactRouterDOM.withRouter(NewReleases); class Home extends React.Component { constructor(props) { super(props); this.state = { newReleases: [], newReleasesLoading: true }; } componentDidMount() { setTimeout(() => { this.setState({ newReleases: [{id: 1, title: "Star Wars"}, {id: 2, title: "Avatar"}], newReleasesLoading: false }); }, 1000); } render() { const { newReleases, newReleasesLoading } = this.state; return ( <NewReleases data={newReleases} loading={newReleasesLoading} /> ) } } class App extends React.Component { render() { const { BrowserRouter, HashRouter, Switch, Route } = ReactRouterDOM; return ( <HashRouter> <Switch> <Route path="/movie/:movieTitle" component={MovieInfo} /> <Route path="/" component={Home} /> </Switch> </HashRouter> ) } } ReactDOM.render(<App />, document.getElementById("root")); 
 <script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script> <script src="https://unpkg.com/react-router-dom/umd/react-router-dom.min.js"></script> <div id="root"></div> 

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM