Jannemannen
Jannemannen

Reputation: 125

How to actually use context?

Edit: Solved using hooks

I've been going at this like crazy. I have a problem where I have to use a prop, in one other component. I understand that I should use contexts.

I've followed some tutorials, but for some reason, I cannot apply it to my own code.

export const FetchContinent = (props) => {
    console.log(props.continent)

return (
    <div>
        {props.continent}
    </div>
)
};

props.continent logs exactly what I want.

I want to use that exact value in this other component, as a prop.

const JumbotronPage = (continent) => {

  return (
    <MDBContainer className="test">
      <MDBRow>
        <MDBCol>
          <MDBJumbotron className="text-center">
            <MDBCardTitle className="card-title h4 pb-2">
              <CountryList
                continent={["NA"]} //<-- HERE!!
                displayFields={["countryName"]}
              />
              <CountryList continent={["NA"]} displayFields={["revName"]} />
            </MDBCardTitle>

So I want to use "props.continent" like (note that this is in another JS-file and another component):

               <CountryList
                continent={[props.continent]} //<-- HERE!!
                displayFields={["countryName"]}
              />

I feel like I've turned every stone but I cannot come up with a solution. I've vacuumed my failed tries to make the code more readable.

Edit: Updated code: FetchContinent:

export const FetchContext = createContext();

export const FetchContinent = (props) => (
    <FetchContext.Provider value={props}>
        {console.log("Fetch continent: " + props.continent)}
      {props.children}
    </FetchContext.Provider>
  );

I'd like to print that value in another file:


const JumbotronPage = (props) => {
  console.log("Props.continent: " + props.continent);
  const value = useContext(FetchContext);
  return (
    <FetchContext.Consumer>
      <h1>{value}</h1>
    </FetchContext.Consumer>
  );
};

However, Value becomes undefined.

Edit 2: This is how I use FetchContinent:

const Africa = ({}) => {
  return (
    <div>
      <VerContinentToolbar before="Memories" continent={["Africa"]} />
      <FetchContinent continent={["Africa"]} />

Edit 3: Full code: JUMBOTRON (Code I had before I started with contexts):

const JumbotronPage = (props) => {

  console.log(<VerContinentToolbar props={props.props} />);
  console.log("fdfds");
  return (
    <MDBContainer className="test">
      <MDBRow>
        <MDBCol>
          <MDBJumbotron className="text-center">
            <MDBCardTitle className="card-title h4 pb-2">
              <CountryList continent="Africa" displayFields={["countryName"]} />
              <CountryList continent="Africa" displayFields={["revName"]} />
              <CountryList
                continent={props.continent}
                displayFields={["countryName"]}
              />
              <CountryList
                continent={props.continent}
                displayFields={["revName"]}
              />
            </MDBCardTitle>

            <MDBCardImage
              src="https://mdbootstrap.com/img/Photos/Slides/img%20(70).jpg"
              src="https://d2gg9evh47fn9z.cloudfront.net/800px_COLOURBOX32059289.jpg"
              className="img-fluid"
            />
            <MDBCardBody>
              hej
              <MDBCardTitle className="indigo-text h5 m-4">
                <CountryList continent="Africa" displayFields={["beerPrice"]} />
                <CountryList continent="Africa" displayFields={["foodPrice"]} />
                <CountryList
                  continent="Africa" 
                  continent={props.continent}
                  displayFields={["beerPrice"]}
                />
                <CountryList
                  continent={props.continent}
                  displayFields={["foodPrice"]}
                />
                <CountryList
                  continent={props.continent}
                  displayFields={["hostelPrice"]}
                />
                <CountryList continent="Africa" displayFields={["dest1"]} />
                <CountryList continent="Africa" displayFields={["dest2"]} />
                <CountryList continent="Africa" displayFields={["dest3"]} />
                <CountryList
                  continent={props.continent}
                  displayFields={["dest1"]}
                />
                <CountryList
                  continent={props.continent}
                  displayFields={["dest2"]}
                />
                <CountryList
                  continent={props.continent}
                  displayFields={["dest3"]}
                />
              </MDBCardTitle>
              <MDBCardText>
                <CountryList continent="Africa" displayFields={["review"]} />

I want:

<CountryList
                  continent={props.continent}
                  displayFields={["dest3"]}
                />

props.continent to be passed from:

const Africa = ({}) => {

  return (
    <div>
      <VerContinentToolbar before="Memories" continent={["Africa"]} />
      <Breadcrumb continent="Africa" />

This component.

Edit 4: So, if I access my component "Africa": https://i.sstatic.net/IwmhO.jpg I'd like to load every country from my database with the continent "Africa"

When I click "Africa", I want to see every country currently in the database associated with Africa: https://i.sstatic.net/4vvnh.jpg

When I click a country, I want to display the information from the database in a jumbotron: https://i.sstatic.net/Khxrv.jpg

The data I'd like to view: https://i.sstatic.net/TwhNa.jpg

Problem is, When I click on a country, I have to set the continent to the corresponding continent. First, I wanted to pass it as a prop, but then I got recommended to use context and now I'm here.

Edit 5: I'm not really sure if contexts is the way to go. A friend recommended me to try it but could not help me further.

Edit 6: Country-list:

import React, { Component, useState, useEffect } from 'react'
import { Link } from 'react-router-dom';
import firebase from '../config'
import './Countries.css'



const useCountries = continent => {
    const [countries, setCountries] = useState([]);
    console.log("Country list: " + continent)

    useEffect(() => {
        firebase
            .firestore()
            .collection(continent[0])
            .onSnapshot((snapshot) => {
                const newCountries = snapshot.docs.map((doc) => ({

                    id: doc.id,

                    ...doc.data()

                }))
                setCountries(newCountries)

            })
    }, [])

    return countries
}



const CountryList = ({ continent, displayFields = [] }) => {
    const countries = useCountries(continent); // <--- Pass it in here

    return (
        <div className="countries">
            {countries.map(country => (
                <div key={country.id}>

                    <div className="entry">

                        {displayFields.includes("continent") && (
                            <div>Name of continent: {country.continent}</div>

                        )}
                        {displayFields.includes("revName") && (
                            <div>{country.revName}</div>
                        )}
                        {displayFields.includes("countryName") && (
                            <div><Link to={"./Jumbotron"}>{country.countryName}</Link></div>
                        )}
                        {displayFields.includes("dest1") && (
                            <div>Destination 1: {country.dest1}</div>
                        )}
                        {displayFields.includes("dest2") && (
                            <div>Destination 2: {country.dest2}</div>
                        )}
                        {displayFields.includes("dest3") && (
                            <div>Destination 3: {country.dest3}</div>
                        )}
                        {displayFields.includes("beerPrice") && (
                            <div>Beer price: {country.beerPrice}</div>
                        )}
                        {displayFields.includes("foodPrice") && (
                            <div>Food price: {country.foodPrice}</div>
                        )}
                        {displayFields.includes("hostelPrice") && (
                            <div>Hostel price: {country.hostelPrice}</div>
                        )}
                        {displayFields.includes("review") && <div>Review: {country.review}</div>}
                        {displayFields.includes("imgUrl") && <img src={country.url} alt="no-img" />}

                    </div>
                </div>

            ))}
        </div>
    );
};


export default CountryList

Thanks,

Upvotes: 1

Views: 694

Answers (3)

brickingup
brickingup

Reputation: 2803

There is library which actually makes it easier to use multiple contexts https://www.npmjs.com/package/react-dynamic-context-provider.

Upvotes: 0

Incepter
Incepter

Reputation: 2948

Assuming that you read the docs about context API and some other sources. I will try to cover some missing parts I had while working with context for my first times.

  1. A Context consists of two parts: A single provider, and consumers. The provider is the part responsible on holding the values, and the consumers will get these values and will be notified with every update.
const MyContext = React.createContext(null);

// bellow code is for illustration only
const MyContextProdiver = ({ children }) => <MyContext.Provider value={{ continentsData: [] }}>{children}</MyContext.Provider>;

const MyContextConsumer = () => {
  const contextValue = React.useContext(MyContext);

  return null; // a react element must be returned here
}

  1. A consumer must be rendered somewhere in the tree in the consumer.
<MyContextProvider>
  <MyAppThatSomewhereHasAConsumer />
  // or may be directly <MyContextConsumer /> but the idea of the context is to be used in multiple parts
</MyContextProvider>

  1. If you have only one consumer, you probably don't need the context and you must locate your state in the right place.

  2. While using Context api across several projects, I use a trick that I find a good practice to notify me if I forgot to put the provider in the right place (also to notify my teammates). I create a hook to use my context, ie: useMyContext()

// this assumes that your context will always start with a truthy value
function useMyContext() {
  const myContextValue = React.useContext(MyContext);
  invariant(myContextValue, 'Have you forgot to put a <MyContext.Provider> somewhere up in the tree ?');
  // or simply put if (!myContextValue) throw new Error('there is no MyContext provider') 
  return myContextValue;
}

// later

const contextValue = useMyContext(); // and you will notified if you are in a tree where there is no provider

Projecting these notes into your requirements, I suggest that:

  1. Make a ContinentProvider that will wrap all consumers and provide them with the countries.
  2. The context may hold data for multiple continents and the consumer choose what he needs.
  3. The context only hold values, it can be more intelligent and provide a fetchContinentData(continentName) function that will on demand fetch a continent countries if not exists already in cached data
  4. You can make a hook that provides directly the continent countries: useContinentCountries that uses the useContinentProvider

Here is a codesandbox demo that looks like this:

codesandbox example

Upvotes: 1

gapsong
gapsong

Reputation: 41

React Context needs a Provider of the Data and a Consumer.

enter image description here

https://de.reactjs.org/docs/context.html#contextprovider

https://de.reactjs.org/docs/context.html#contextconsumer

Upvotes: 0

Related Questions