Search code examples
reactjsreact-context

React Context: TypeError: Cannot read property 'areResultsVisible' of undefined


Can someone please help me? I'm trying to pass data through context from one component to another (from Search.js to Container.js). However, I'm getting a type error. I searched many questions in Web, but haven't found an answer. Sorry, if it's childish problem, I'm new.

Search.js:

import React, { Component } from 'react'
import { StyledFormSearchBar } from '../styles'
import { data } from '../data'

const SearchContext = React.createContext()

export default class Search extends Component {
  constructor() {
    super()
    this.state = {
      value: '',
      results: [],
      areResultsVisible: false
    }

    this.handleSearch = this.handleSearch.bind(this)
  }

  handleSearch(e) {
    this.setState = {
      value: e.target.value,
      results: data.filter(item => {
        return item.title.toLowerCase().includes(e.target.value.toLowerCase())
      }),
      areResultsVisible: true
    }
  }

  render() {
    return (
      <StyledFormSearchBar>
        <input type="search" name="search" className="border border-dark rounded" onSubmit={this.handleSearch}/>
        <button type="submit" value="submit" className="bg-warning border-0 text-danger rounded-right position-relative">
          <i className="fas fa-search"></i>
        </button>

        <SearchContext.Provider value = {{
          ...this.state,
          handleSearch: this.handleSearch
        }}>
          {this.props.children}
        </SearchContext.Provider>
        
      </StyledFormSearchBar>
    )
  }
}

const SearchContextConsumer = SearchContext.Consumer

export { SearchContextConsumer }
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>

Container.js:

import React from 'react'
import { Route, Switch } from "react-router-dom"
import { StyledDivGridContainer } from './styles'
import { SearchContextConsumer } from './header/Search'

import Carousel from './container/Carousel'
import MobilePhonesDiscount from './container/products/carousel/MobilePhonesDiscount'
import LaptopsDiscount from './container/products/carousel/LaptopsDiscount'
import TabletsDiscount from './container/products/carousel/TabletsDiscount'
import Products from './container/Products'
import SearchResults from './container/SearchResults'
import MobilePhones from './container/products/MobilePhones'
import Laptops from './container/products/Laptops'
import Tablets from './container/products/Tablets'
import ProductPage from './container/ProductPage'
import About from './container/About'
import ContactUs from './container/ContactUs'

export default function Container() {
  return (
    <StyledDivGridContainer>
      <div className="no-gutters justify-content-between">
        <SearchContextConsumer>
          {
            value => {
              return (
                !value.areResultsVisible 
                ? <Switch>
                    <Route exact path="/" component={Carousel}/>
                    <Route exact path="/" component={Products}/>
                  </Switch>                
                : <Route exact path="/" component={SearchResults}/>
              )
            }
          }
        </SearchContextConsumer>
        <Route path="/mobile_phones_discount" component={MobilePhonesDiscount}/>
        <Route path="/laptops_discount" component={LaptopsDiscount}/>
        <Route path="/tablets_discount" component={TabletsDiscount}/>
        <Route path="/mobile_phones" component={MobilePhones}/>
        <Route path="/laptops" component={Laptops}/>
        <Route path="/tablets" component={Tablets}/>
        <Route path="/product_page" component={ProductPage}/>
        <Route path="/about" component={About}/>
        <Route path="/contact_us" component={ContactUs}/>
      </div>
    </StyledDivGridContainer>
  )
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>

Search.js is in Header.js:

import React, { Component } from 'react'
import { StyledHeader,
         StyledSpanAccount } from './styles'
import { Link } from "react-router-dom"

import Catalogue from "./header/Catalogue"
import Search from "./header/Search"

export default class Header extends Component {
  render() {
    return (
      <StyledHeader className="d-flex w-100 bg-light shadow justify-content-center">
        <div className="d-flex flex-wrap justify-content-around align-items-center">
          <div className="my-1 mr-3">
            <Link to="/" className="logo">
              <img src={require('../img/logo.webp')} alt="logo" className="img-tumbnail"/>
            </Link>
          </div>
          <Catalogue/>

          <Search/>
          
          <div className="d-flex my-3">
            <a href="#"><i className="fas fa-shopping-cart"></i></a>
            <a href="#"><i className="fas fa-user-alt ml-3"></i></a>
            <a href="#" className="d-flex flex-nowrap">
              <StyledSpanAccount className="ml-2">Log in</StyledSpanAccount>
            </a>
            <a href="#" className="d-flex flex-nowrap">
              <StyledSpanAccount className="ml-2">Sing up</StyledSpanAccount>
            </a>
          </div>
        </div>
      </StyledHeader>
    )
  }
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>

Container.js is in App.js near Header.js:

import React from 'react';
import { Route } from "react-router-dom";
import { StyledDivWrapper } from './components/styles';

import Header from './components/Header';
import Container from './components/Container';
import Footer from './components/Footer';

export default function App() {
  return (
    <StyledDivWrapper className="d-flex flex-column">
      <Route path="/" component={Header}/>
      <Route path="/" component={Container}/>
      <Route path="/" component={Footer}/>
    </StyledDivWrapper>
  );
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>

Error screenshot My folder tree


Solution

  • The problem is that your Container component is not within your SearchContext.Provider. To make sure your code works correctly you must have your SearchContext Provider directly inside you App Component so that it is a common parent of both Search and Container component

    Firstly, create a context in a different file

    // SearchContext.js
    
    export const SearchContext = React.createContext();
    const SearchContextConsumer = SearchContext.Consumer
    
    export { SearchContextConsumer }
    

    Secondly, create a component which acts as a provider

    // SearchProvider.js
    
    class SearchProvider extends React.Component {
       constructor() {
        super()
        this.state = {
          value: '',
          results: [],
          areResultsVisible: false
        }
    
        this.handleSearch = this.handleSearch.bind(this)
      }
    
      handleSearch(e) {
        this.setState = {
          value: e.target.value,
          results: data.filter(item => {
            return item.title.toLowerCase().includes(e.target.value.toLowerCase())
          }),
          areResultsVisible: true
        }
      }
    
      render() {
        return (
           <SearchContext.Provider value = {{
              ...this.state,
              handleSearch: this.handleSearch
            }}>
              {this.props.children}
           </SearchContext.Provider>        
        )
      }
    }
    

    Now you Search Component would simply be

    // Search.js
    
    
    export default class Search extends Component {
      render() {
        return (
           <SearchContext.Consumer>
             {({handleSearch}) => (
                 <StyledFormSearchBar>
                    <input type="search" name="search" className="border border-dark rounded" onSubmit={handleSearch}/>
                    <button type="submit" value="submit" className="bg-warning border-0 text-danger rounded-right position-relative">
                      <i className="fas fa-search"></i>
                    </button>
                  </StyledFormSearchBar>
              )}
            </SearchContext.Consumer>
        )
      }
    }
    

    Also no you can use your SearchProvider in App.js like

    export default function App() {
        return (
           <SearchProvider>
              <StyledDivWrapper className="d-flex flex-column">
                  <Route path="/" component={Header}/>
                  <Route path="/" component={Container}/>
                  <Route path="/" component={Footer}/>
              </StyledDivWrapper>
           </SearchProvider>
        );
    }