Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to use the React Context API?

I have a React App (setup with CRA) with a global element (custom cursor, that is a rounded div following the mouse) whose style I want to update/change when hovering various other components that are nested differently and more or less deeply (in the structure provided below I am only listing one example component). From what I understand this is a good use case for Context API.

The structure of my App looks like this (simplified):

<Cursor />
<Layout>
  <Content>
    <Item />
  </Content>
</Layout>

So when hovering <Item /> (amongst other components) I want to update the style of the <Cursor /> component.

Therefore I tried to access a function I set up inside my <Cursor /> component in my <Item /> component. Unfortunately when hovering it does not update my state and as consequence the style of my <Cursor /> does not change.

My Cursor component looks like this (simplified):

import React, { Component } from "react"
export const CursorContext = React.createContext(false)

class Cursor extends Component {

  constructor(props) {
    super(props)
    this.state = {
      positionX: 0,
      positionY: 0,
      scrollOffsetY: 0,
      display: "none",
      isHoveringProjectTeaserImage: false,
    }
    this.handleMousePosition = this.handleMousePosition.bind(this)
    this.handleMouseOverProjectTeaser = this.handleMouseOverProjectTeaser.bind(this)
    this.handleMouseLeaveProjectTeaser = this.handleMouseLeaveProjectTeaser.bind(this)
  }

  handleMousePosition = (mouse) => {
    this.setState({
      positionX: mouse.pageX,
      positionY: mouse.pageY,
      display: "block",
      scrollOffsetY: window.pageYOffset
    })
  }

  handleMouseOverProjectTeaser = () => {
    this.setState({
      isHoveringProjectTeaserImage: true
    })
  }

  handleMouseLeaveProjectTeaser = () => {
    this.setState({
      isHoveringProjectTeaserImage: false
    })
  }

  componentDidMount() {
    document.body.addEventListener("mousemove", this.handleMousePosition)
  }

  componentWillUnmount() {
    document.body.removeEventListener("mousemove", this.handleMousePosition)
  }

  render() {

    const {
      positionX,
      positionY,
      display,
      scrollOffsetY,
      isHoveringProjectTeaserImage
    } = this.state

    return(

    <CursorContext.Provider value={this.state}>
      <div>
        <StyledCursor
          style={ isHoveringProjectTeaserImage
                  ? {backgroundColor: "red", display: `${display}`, top: `${positionY - scrollOffsetY}px`, left: `${positionX}px`}
                  : {backgroundColor: "yellow", display: `${display}`, top: `${positionY - scrollOffsetY}px`, left: `${positionX}px`}}
        />
      </div>
    </CursorContext.Provider>
    )
  }

}

export default Cursor

And my Item Component that can be hovered looks like this (simplified):

import React, { Component } from "react"
import { CursorContext } from '../Cursor/Index';

class Item extends Component {
  constructor(props) {
    // not showing stuff in here that's not relevant
  }

  static contextType = CursorContext

  render() {
    return(
      <CursorContext.Consumer>
        {(value) =>
          <StyledItem
            onMouseOver={value.handleMouseOverProjectTeaser}
            onMouseLeave={value.handleMouseLeaveProjectTeaser}
          >
          </StyledItem>
        }
      </CursorContext.Consumer>
    )
  }

}

export default Item

Do I even need to use static contextType = CursorContext?

When not passing a default value (I thought they are optional anyway) I am getting an TypeError: Cannot read property 'handleMouseOverProjectTeaser' of undefined, as soon as I pass a long a false as default value my App renders but does not update my <Cursor /> state.

Am I even using Context API correctly?

like image 507
Christoph Berger Avatar asked Nov 18 '25 03:11

Christoph Berger


1 Answers

React.createContext default value?

As you correctly stated, the value passed to React.createContext() does not matter in this case.

When not passing a default value (I thought they are optional anyway) I am getting an TypeError: Cannot read property 'handleMouseOverProjectTeaser' of undefined, as soon as I pass a long a false as default value my App renders but does not update my state.

This brings out the fact that your default value is always used: try running undefined.blahblah vs. false.blahblah: the former throws a TypeError while the second silently returns undefined.

So we know the value you set in <Provider value={...}> never reaches the consumer, but why?

The Context is only available to its descendants

The <C.Consumer> is not rendered as a descendant of the <C.Provider>, so it can't get access to it. In other words, the provider should "enclose" the consumers. From the docs:

Context is designed to share data that can be considered “global” for a tree of React components [...]

The root of that tree is your <C.Provider>, and in your case, the consumer is not part of that tree.

Something like that could work:

<CursorContext>
  <StyledCursor />
  <Layout>
    <Content>
      <Item />
    </Content>
  </Layout>
</CursorContext>

Misc.

Do I even need to use static contextType = CursorContext?

Not really, if you're using <CursorContext.Consumer>. From the docs:

Context.Consumer: A React component that subscribes to context changes.

But in your case, since you don't need to listen to context changes (from your example code anyways), just keep the static contextType:

  static contextType = CursorContext

  render() {
    return(
      <StyledItem
        onMouseOver={this.context.handleMouseOverProjectTeaser}
        onMouseLeave={this.context.handleMouseLeaveProjectTeaser}
      >
      </StyledItem>
    )
  }

The point is you should use one or the other, you don't need both.


Last thing, you're passing this.state in the provider, and using this.context.handleMouseOverProjectTeaser in the child component... but there is no such function in <Cursor>'s state. Maybe you intended to pass <Cursor> itself, or better, just the handlers?

like image 128
hugo Avatar answered Nov 21 '25 10:11

hugo



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!