javascript - How to access a prop in a layout component if my component is being passed to the layout component as a chilren pro

I have a Layout ponent that contains a state called isLightMode, which I would like to pass as a prop f

I have a Layout ponent that contains a state called isLightMode, which I would like to pass as a prop for other ponents to access and use.

Here is my Layout ponent:

import React from 'react'
import Main from '../ponents/main'

export default class Layout extends React.Component {
  constructor(props) {
    super(props)
    this.state = { isLightMode: true }
    this.toggleTheme = this.toggleTheme.bind(this)
  }

  toggleTheme() {
    this.setState(state => ({
      isLightMode: !state.isLightMode,
    }))
  }

  render() {
    const { isLightMode } = this.state
    const { children } = this.props
    return (
      <div>
        <Navigation
          isLightMode={isLightMode}
          onToggleTheme={this.toggleTheme}
        />
        {children && <Main isLightMode={isLightMode}>{children}</Main>}
      </div>
    )
  }
}

In particular, I have a Home ponent that I would like to access the isLightMode prop:

import React from 'react'
import Layout from '../layouts/layout'
import AvatarLight from '../../content/assets/img/avatar-light.png'
import AvatarDark from '../../content/assets/img/avatar-dark.svg'

const Home = props => {
  return (
    <Layout>
      <img src={props.isLightMode ? AvatarLight : AvatarDark} />
    </Layout>
  )
}
export default Home

And here is my Main ponent where I can access props.isLightMode, but I want to access it from Home ponent:

import React from 'react'

const Main = (props) => {
  return (
    <main className="main">
      <div>lightmode:{props.isLightMode}</div>
      <div className="container">{props.children}</div>
    </main>
  )
}
export default Main

So how can I access the isLightMode prop that is in the Layout ponent from my Home ponent?

I have a Layout ponent that contains a state called isLightMode, which I would like to pass as a prop for other ponents to access and use.

Here is my Layout ponent:

import React from 'react'
import Main from '../ponents/main'

export default class Layout extends React.Component {
  constructor(props) {
    super(props)
    this.state = { isLightMode: true }
    this.toggleTheme = this.toggleTheme.bind(this)
  }

  toggleTheme() {
    this.setState(state => ({
      isLightMode: !state.isLightMode,
    }))
  }

  render() {
    const { isLightMode } = this.state
    const { children } = this.props
    return (
      <div>
        <Navigation
          isLightMode={isLightMode}
          onToggleTheme={this.toggleTheme}
        />
        {children && <Main isLightMode={isLightMode}>{children}</Main>}
      </div>
    )
  }
}

In particular, I have a Home ponent that I would like to access the isLightMode prop:

import React from 'react'
import Layout from '../layouts/layout'
import AvatarLight from '../../content/assets/img/avatar-light.png'
import AvatarDark from '../../content/assets/img/avatar-dark.svg'

const Home = props => {
  return (
    <Layout>
      <img src={props.isLightMode ? AvatarLight : AvatarDark} />
    </Layout>
  )
}
export default Home

And here is my Main ponent where I can access props.isLightMode, but I want to access it from Home ponent:

import React from 'react'

const Main = (props) => {
  return (
    <main className="main">
      <div>lightmode:{props.isLightMode}</div>
      <div className="container">{props.children}</div>
    </main>
  )
}
export default Main

So how can I access the isLightMode prop that is in the Layout ponent from my Home ponent?

Share Improve this question asked Jul 24, 2019 at 21:12 kimbaudikimbaudi 15.7k9 gold badges68 silver badges80 bronze badges 4
  • 5 You have to either pass the prop down through ponents (known as prop drilling), or use the context API reactjs/docs/context.html. – Tom Commented Jul 24, 2019 at 21:15
  • @Tom - So before context API, there would be no way for me to access isLightMode from Home ponent since I am not passing the prop directly from Layout to Home? Just trying to understand if there are also other possible ways (even if they are less efficient) than Context API – kimbaudi Commented Jul 24, 2019 at 22:14
  • You could also use Redux but again, that uses the context API. The only other way is prop drilling. – Tom Commented Jul 24, 2019 at 23:33
  • you can make use of React hook's useContext – Joseph D. Commented Jul 25, 2019 at 1:14
Add a ment  | 

4 Answers 4

Reset to default 3

Context

Use createContext to wrap all ponents that you need to access the state. And consume it via Consumer (much cleaner with hooks).

const Theme = React.createContext({ isLightMode: true });

// Consume the context in Home and in Layout
const App = () => (
  <Theme.Provider>
    <Home />
  </Theme.Provider>
);

Pass reference

This is an example with hooks, can be achieved with classes.

const Home = props => {
  const isLightRef = createRef(false);
  // In layout call props.isLightPref.current = true / false.
  return (
    <Layout isLighRef={isLightRef}>
      <img src={isLightRef.current ? AvatarLight : AvatarDark} />
    </Layout>
  );
};

Pass properties

Home
  --- Layout
         --- Navigation
         --- Main

How your code is structured isLightMode needs to be in Home so it will be in scope for Layout too:

export default class Home extends React.Component {
  constructor(props) {
    super(props);
    this.state = { isLightMode: true };
    this.toggleTheme = this.toggleTheme.bind(this);
  }

  toggleTheme() {
    this.setState(state => ({
      isLightMode: !state.isLightMode
    }));
  }

  render() {
    return (
      <Layout isLightMode={this.toggleTheme} toggleTheme={this.toggleTheme}>
        <img src={this.isLightMode ? AvatarLight : AvatarDark} />
      </Layout>
    );
  }
}

const Layout = ({ children, isLightMode }) => {
  return (
    <div>
      <Navigation isLightMode={isLightMode} onToggleTheme={this.toggleTheme} />
      {children && <Main isLightMode={isLightMode}>{children}</Main>}
    </div>
  );
};

State Manager

When context bees too overwhelming you should consider using state managing libraries like Redux and MobX.

The better way is to make context and consumer from there. But you can use React.cloneElement for passing data :

So in your case, you can do something like this :

class Layout extends React.Component {
  constructor(props) {
    super(props);
    this.state = { isLightMode: true };
    this.toggleTheme = this.toggleTheme.bind(this);
  }

  toggleTheme() {
    this.setState(state => ({
      isLightMode: !state.isLightMode
    }));
  }

  render() {
    const { isLightMode } = this.state;
    const { children } = this.props;

    return (
      <div>{children && <Main isLightMode={isLightMode}>{children}</Main>}</div>
    );
  }
}

And in your home ponent you can consume directly :

const Main = props => {
  return (
    <main className="main">
      <div>lightmode:{String(props.isLightMode)}</div>
      <div className="container">
        {React.cloneElement(props.children, props)} // pass props data to children
      </div>
    </main>
  );
};
const Home = props => {
  return <>{props.isLightMode ? "AvatarLight" : "AvatarDark"}</>;
};

Here is working link : https://codesandbox.io/s/busy-resonance-x69gm

You can get it done by passing a callback function as a prop from the Home Component to the Layout Component, so as soon as the state of the isLightMode gets changed you can call that function and change the isLightMode value in the Home.

const Home = props => {
  return (
    <Layout
      onChangeMode={(mode) => setLightMode(mode)}
    >
      <img src={props.isLightMode ? AvatarLight : AvatarDark} />
    </Layout>
  )
}

So the toggleTheme will bee,

toggleTheme() {
  this.setState(state => ({
    isLightMode: !state.isLightMode,
  }), () => {
    this.props.onChangeMode(this.state.isLightMode);
  })
}

In the Home Component, you will have to use useState hook or state to maintain the value of the isLightMode and pass it to the img tag from it.

Hope so this is what was required by you, and it solves your issue.

Solution using Context API:

Steps:

  1. create Context object and Context.Provider ponent
  2. wrap your root element in the Context.Provider ponent
  3. use contextType property (or Context.Consumer) to consume context

1. create Context object and Context.Provider ponent:

import react from 'React'

const defaultValue = {
  isLightMode: true,
  toggleTheme: () => {},
}

// create Context object called ThemeContext
const ThemeContext = React.createContext(defaultState)

// create Context.Provider ponent called ThemeProvider
class ThemeProvider extends React.Component {
  state = {
    isLightMode: true,
  }

  toggleTheme = e => {
    e.preventDefault()
    const isLightMode = !this.state.isLightMode
    localStorage.setItem('isLightMode', JSON.stringify(isLightMode))
    this.setState({ isLightMode })
  }

  ponentDidMount() {
    const isLightMode = JSON.parse(localStorage.getItem('isLightMode'))
    if (isLightMode) {
      this.setState({ isLightMode })
    }
  }

  render() {
    const { children } = this.props
    const { isLightMode } = this.state

    return (
      <ThemeContext.Provider
        value={{
          isLightMode,
          toggleTheme: this.toggleTheme,
        }}
      >
        {children}
      </ThemeContext.Provider>
    )
  }

}

2. wrap your root element in the Context.Provider ponent:

import React from 'react'
import { ThemeProvider } from './src/context/ThemeContext'
export const wrapRootElement = ({ element }) => (
  <ThemeProvider>{element}</ThemeProvider>
)

3. use contextType property to consume context:

Home ponent

import React from 'react'
import Layout from '../layouts/layout'
import ThemeContext from '../context/ThemeContext'
import AvatarLight from '../../content/assets/img/avatar-light.png'
import AvatarDark from '../../content/assets/img/avatar-dark.svg'

export default class Home extends React.Component {
  static contextType = ThemeContext

  render() {
    const { isLightMode } = this.context
    return (
      <Layout>
        <img src={isLightMode ? AvatarLight : AvatarDark} />
      </Layout>
    )
  }
}

Navigation ponent

import React from 'react'
import Layout from '../layouts/layout'
import ThemeContext from '../context/ThemeContext'
import AvatarLight from '../../content/assets/img/avatar-light.png'
import AvatarDark from '../../content/assets/img/avatar-dark.svg'

export default class Home extends React.Component {
  static contextType = ThemeContext

  render() {
    const { isLightMode, toggleTheme } = this.context
    return (
      <nav>
        <a onClick={toggleTheme}>{isLightMode ? 'Light mode' : 'Dark mode'}</a>
      </nav>
    )
  }
}

发布者:admin,转转请注明出处:http://www.yc00.com/questions/1744306784a4567752.html

相关推荐

发表回复

评论列表(0条)

  • 暂无评论

联系我们

400-800-8888

在线咨询: QQ交谈

邮件:admin@example.com

工作时间:周一至周五,9:30-18:30,节假日休息

关注微信