简体   繁体   中英

How would I restructure my React App, so that I can pass state down to a component on a different route

I have three components routed to different paths. I want to restructure my App so that I can pass state via props from my SubmitProject Component to my Portfolio Component I still want them to have separate paths ie; /portfolio and /SubmitProject I plan to have two browserwindows open to test that when I submit a form on SubmitProject it will show up on Portfolio then I will be using firebase to persist my state to a database.

Do I need to have my state be at a top level Component like App.js and then have the BrowserRouter inside of that? If so how do I recreate the connections I have made from <SubmitProject/> -> <PortfolioForm/> -> <FormAdd/>

My Desired Goal is that when I submit the form from the FormAdd Component when I am on the /submit Route that it will output via state on my Portfolio Component on the /Portfolio Route.

It has been recommend to use a state manager like context api, or something else, but I want to know if there is a way to restructure my App and be able to pass state from a top level component that each component and route share.

Here is my relevant code

components/Router.js

import React from 'react';
import {BrowserRouter, Route, Switch} from 'react-router-dom';
import Portfolio from './Portfolio';
import SubmitProject from './SubmitProject';
import App from './App';

const Router = () => (
    <BrowserRouter>
        <Switch>
            <Route exact path="/" component={App}/>
            <Route exact path="/portfolio" component={Portfolio}/>
            <Route exact path="/submit" component={SubmitProject}/>
        </Switch>
    </BrowserRouter>
);

export default Router;

components/App.js // Should My Router be in here?

import React from 'react';

class App extends React.Component {

  render() {
      return <div>Test</div>
  }

}

export default App;

/components/SubmitProject.js

import React from 'react';
import PortfolioForm from './PortfolioForm';
import Section from './Section';

class SubmitProject extends React.Component {
    state = {
        sections:{}
    };
    addSection = section =>{
        const sections = {...this.state.sections};
        sections[`section${Date.now()}`] = section;
        this.setState({
            sections: sections
        });
    }
    render() {
        return(
            <React.Fragment>
                <h1>Submit Project</h1>
                <h2>Enter Project Data</h2>
                <ul className="section">
                    {Object.keys(this.state.sections).map(key => <Section key={key} details={this.state.sections[key]}/>)}
                </ul>
                <PortfolioForm addSection={this.addSection} />
            </React.Fragment>
        )
    }
}

export default SubmitProject;

/components/PortfolioForm.js

import React from 'react';
import FormAdd from './FormAdd';

class Portfolio extends React.Component {
    render() {
        return(
            <React.Fragment>
                <h1>Submit Form</h1>
                <FormAdd addSection={this.props.addSection}/>
            </React.Fragment>
        )
    }
}

export default Portfolio;

/components/FormAdd.js

import React from 'react';

class FormAdd extends React.Component {
    nameRef = React.createRef();

    createSection = (event) =>{
        event.preventDefault();
        const section = {
            name: this.nameRef.current.value
        };
        this.props.addSection(section);
    };  
    render() {
        return(
            <React.Fragment>
                <form onSubmit={this.createSection}>
                    <input type="text" ref={this.nameRef} name="name" placeholder="Name"/>
                    <button type="submit">+ Add Section</button>
                </form>
            </React.Fragment>
        )
    }
}

export default FormAdd;

/components/Portfolio.js

import React from 'react';

class Portfolio extends React.Component {
    //CAN I GET STATE FROM SubmitProject.js FILE IN HERE? By Restructuring my App Somehow.
    render() {
        return(
            <React.Fragment>
                <h1>Portfolio Page</h1>
                <h2>List of projects</h2>     
            </React.Fragment>
        )
    }
}

export default Portfolio;

UPDATED CODE I am now getting an error that says FooContext is not defined

components/App.js

import React from 'react';
import SubmitProject from './SubmitProject';
import {BrowserRouter, Route, Switch} from 'react-router-dom';

const FooContext = React.createContext();

class App extends React.Component {
  state = {
    sections:{}
  };
  addSection = section =>{
      const sections = {...this.state.sections};
      sections[`section${Date.now()}`] = section;
      this.setState({
          sections: sections
      });
  }

  render() {
      return (
        <FooContext.Provider value={this.state.sections}>
          <Router/>;
        </FooContext.Provider>
      )
  }

}

class Router extends React.PureComponent {
  render() {
    return 
    <BrowserRouter>
        <Switch>
            <Route exact path="/" component={Root} />      
        </Switch>
    </BrowserRouter>
  }
}

const Root = props => <FooContext.Consumer>{sections => <SubmitProject/> }</FooContext.Consumer>;

export default App;

UPDATED CODE V#2 App.js

import React from 'react';
import SubmitProject from './SubmitProject';
import Home from './Home';
import {BrowserRouter, Route, Switch} from 'react-router-dom';

const FooContext = React.createContext();

class App extends React.Component {
  state = {
    sections:{}
  };
  addSection = section =>{
      const sections = {...this.state.sections};
      sections[`section${Date.now()}`] = section;
      this.setState({
          sections: sections
      });
  }

  render() {
      return (
        <FooContext.Provider value={this.state.sections}>
          <Router/>;
        </FooContext.Provider>
      )
  }

}

class Router extends React.PureComponent {
  render() {
    return (
      <BrowserRouter>
        <Switch>
            <Route exact path="/" component={Home} /> 
            <Route exact path="/portfolio" component={Portfolio} />      
        </Switch>
      </BrowserRouter> 
    )

  }
}

const Portfolio = props => <FooContext.Consumer>{foo => <SubmitProject/>}</FooContext.Consumer>;

export default App;

SubmitProject.js

import React from 'react';
import PortfolioForm from './PortfolioForm';
import Section from './Section';

class SubmitProject extends React.Component {

    render() {
        return(
                <React.Fragment>
                    <h1>Submit Project</h1>
                    <h2>Enter Project Data</h2>
                    <ul className="section">
                        {Object.keys(this.state.sections).map(key => <Section key={key} details={this.state.sections[key]}/>)}
                    </ul>
                    <PortfolioForm addSection={this.addSection} />
                </React.Fragment>   

        )
    }
}

export default SubmitProject;

It has been recommend to use a state manager like context api, or something else, but I want to know if there is a way to restructure my App and be able to pass state from a top level component that each component and route share.

There are problems with this approach.

Considering that App maintains application state, it's necessary to pass it to <Router> as a prop and then to route components that depend on it:

class App extends React.Component {
  state = { foo: true };

  render() {
      return <Router foo={this.state.foo}/>
  }
}

const Router = props => (
    const RootWithFoo = props => <Root foo={props.foo}/>;

    return <BrowserRouter>
        <Switch>
            <Route exact path="/" component={RootWithFoo} />
            ...
        </Switch>
    </BrowserRouter>
);

This puts a restriction on component structure; in order to avoid deeply passed props, Router component should be removed, and Route should be rendered directly in App :

class App extends React.Component {
  state = { foo: true };

  render() {
    const RootWithFoo = props => <Root foo={this.state.foo}/>;

    return <BrowserRouter>
        <Switch>
            <Route exact path="/" component={RootWithFoo} />
            ...
        </Switch>
    </BrowserRouter>
  }
}

This is a problem that context API and state management libraries (eg Redux) address. They allow to provide global state for nested components where it's used.

Another problem is performance. The whole router will be re-rendered on each state update. Again, context API and state management libraries address this. As context API manual states:

All Consumers that are descendants of a Provider will re-render whenever the Provider's value prop changes. The propagation from Provider to its descendant Consumers is not subject to the shouldComponentUpdate method, so the Consumer is updated even when an ancestor component bails out of the update.

So if context provider value updates, it's unnecessary to re-render the whole tree. Context consumer will be re-rendered regardless of this. Since the whole tree will be re-rendered by default, Provider child(ren) should be a pure component to avoid unnecessary re-renders. This is a reason why separated App and Router components may be preferable:

class App extends React.Component {
  state = { foo: true };

  render() {
    return <FooContext.Provider value={this.state.foo}>
      <Router/>;
    </FooContext.Provider>
  }
}

class Router extends React.PureComponent {
  render() {
    return <BrowserRouter>
        <Switch>
            <Route exact path="/" component={Root} />
            ...
        </Switch>
    </BrowserRouter>
  }
}

const Root = props => <FooContext.Consumer>{foo => ...}</FooContext.Consumer>;

When global state is updated, only App and route components that depend on it ( Root , etc.) are re-rendered but not Router .

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