Snig501
Snig501

Reputation: 174

Can't access context values in React

I'm trying to pass a value from one component within a context consumer to another component as a prop it says it's undefined.

<ReadingSessionContext.Consumer>
  {(context) => {
    console.dir(context.state.session.duration) // Value is printed here
    return (
      ...
        <Timer duration={context.state.session.duration} />
      ...
    )
  }}
</ReadingSessionContext.Consumer>

And the Timer component

class Timer extends React.Component {
  state = {
    "minutes": this.props.duration.split(":")[1].parseInt(), // Returns error here
    "seconds": this.props.duration.split(":")[2].parseInt()  // Returns error here
  }

  componentDidMount() {
    console.dir(this.props) // Value is undefined here
    this.myInterval = setInterval(() => {
      const { seconds, minutes } = this.state;

      if (seconds > 0) {
        this.setState(({ seconds }) => ({
          seconds: seconds - 1
        }))
      }

      if (seconds === 0) {
        if (minutes === 0) {
          clearInterval(this.myInterval)
        } else {
          this.setState(({ minutes }) => ({
            minutes: minutes - 1,
            seconds: 59
          }))
        }
      }
    }, 1000)
  }

  render() {
    const { minutes, seconds } = this.state;

    return (
      <Typography component="h1" variant="h5">
        Time Remaining: { minutes }:{ seconds < 10 ? `0${ seconds }` : seconds }
      </Typography>
    )
  }
}

I've also tried to refactor this so that the Timer component consumes the context, rather than passing it as a prop:

function Timer() {
  const context = useContext(ReadingSessionContext);
  const [minutes, setMinutes] = useState(3);
  const [seconds, setSeconds] = useState(0);

  useEffect(() => {
    let duration = context.state.session.duration; // Value is accessable here
    console.log(duration); // This logs to the console correctly, and it is a string
    setMinutes(duration.split(":")[1].parseInt()); // Throws error here
    setSeconds(duration.split(":")[2].parseInt()); // Throws error here

    let timerInterval = setInterval(() => {
      if (seconds > 0) {
        setSeconds(seconds - 1);
      }

      if (seconds === 0) {
        if (minutes === 0) {
          clearInterval(this.timerInterval)
        } else {
          setMinutes(minutes - 1);
          setSeconds(59)
        }
      }
    }, 1000);
  });
}

In each case, the error that gets thrown is - Uncaught TypeError: Cannot read property 'split' of undefined

However, in every instance where I inspect the value before calling .split() it tells me that the value exists, and is in fact correct, but all of a sudden stops existing as soon as I try to perform some action on the string

Provider.jsx

import React from "react";
import axios from "axios";
import ReadingSessionContext from "./Context";

export default class ReadingSessionProvider extends React.Component {

  /**
   * Set the initial state of the `ReadingSessionProvider`
   * @param {*} props 
   */
  state = {
    "translationUrl": process.env.REACT_APP_BACKEND_URL + "translate/",
    "readingSessionUrl": process.env.REACT_APP_BACKEND_URL + "reading-sessions/",
    "session": {},
    "book": {},
    "translations": [],
    "serverPage": 1,
    "clientPage": 0,
    "limit": 10,
    "totalResults": 0,
    "sessionId": 0,
    "headers": {
      "Content-type": "application/json",
      "Authorization": "Token " +  localStorage.getItem("token"),
    }
  }

  /**
   * After the component mounts, call the `getReadingSession` method
   * and update the state with response
   */
  async componentDidMount() {
    let data = await this.getReadingSession();
    this.setState({"session": data.data});
    this.setState({"book": data.data.library_item.book});
    await this.getTranslations()
  }

  /**
   * Call the API and get data for this specific reading session
   */
  async getReadingSession() {
    let result = await axios.get(
      this.state.readingSessionUrl + window.location.href.split('/')[5] + "/",
      {headers: this.state.headers}
    );
    return result;
  }

  makeUrl = sessionId => {
    return `${this.state.translationUrl}?page=${this.state.serverPage}&limit=${this.state.limit}&sessionId=${this.state.session.id}`;
  }

  /**
   * Make the API call to the server to retrieve a list of the translations
   * for the currently logged in user.
   */
  getTranslations = async () => {
    try {
      let url = `${this.state.translationUrl}?page=${this.state.serverPage}&limit=${this.state.limit}&sessionId=${this.state.session.id}`
      let response = await axios.get(url, {headers: this.state.headers});
      await this.setState({"translations": response.data.results});
      await this.setState({"totalResults": response.data.count});
    } catch (error) {
      console.log(error);
    }
  }

  /**
   * Submit the text that the user has input and get the updated 
   * list of translations from the API
   */
  submitText = async (e, text) => {
    console.log("hello?")
    let data = {
      "text_to_be_translated": text,
      "session": this.state.session.id
    };

    try {
      await axios.post(this.state.translationUrl, data, {headers: this.state.headers});
      let paginationUrl = `${this.state.translationUrl}?page=${this.state.serverPage}&limit=${this.state.limit}&sessionId=${this.state.session.id}`;
      this.getTranslations(paginationUrl);
    } catch (error) {
      console.dir(error);
    }
  }

  setSessionId = sessionId => {
    this.setState({"sessionId": sessionId});
    console.log("called")
  }

  handleChangePage = async (event, newPage) => {
    this.setState({"serverPage": newPage + 1})
    this.setState({"clientPage": newPage})
    let url = await `${this.state.translationUrl}translate/?page=${newPage + 1}&limit=${this.state.limit}&sessionId=${this.state.session.id}`
    console.log(url)
    await this.getTranslations(url);
  }

  render() {
    return (
      <ReadingSessionContext.Provider value={{
        state: this.state,
        getTranslations: this.getTranslations,
        submitText: this.submitText,
        handleChangePage: this.handleChangePage,
        setSessionId: this.setSessionId,
        makeUrl: this.makeUrl

      }}>
        {this.props.children}
      </ReadingSessionContext.Provider>
    )
  }
}

Context.jsx

import React from "react";

const ReadingSessionContext = React.createContext();

export default ReadingSessionContext;

Upvotes: 0

Views: 2849

Answers (1)

Chukwuma Zikora
Chukwuma Zikora

Reputation: 109

Okay. You're really close. You still need to wrap your root component or hierachy with ReadingSessionProvider. So this is how it works generally.

  1. You create the context with const ReadingSessionContext = React.createContext();
  2. You use it to make a context provider. Which you have done when you created the the ReadingSessionProvider class.
  3. You use the ReadingSessionProvider wrap the app section you want to access the consumer. So say
<ReadingSessionProvider>
  <App />
</ReadingSessionProvider>
  1. Now the children of App component can use the values set in the ReadingSessionProvider. Either with useContext(ReadingSessionContext) or with ReadingSessionContext.Consumer

See this video from Wes Bos for more information: https://youtu.be/XLJN4JfniH4

Upvotes: 1

Related Questions