2

I need to send selectedCoin state from Company to details.js How do I do this with context?

company component:

import React, { useState, useEffect } from "react";
import { Col, Image, Row } from "react-bootstrap";
import "./Company.scss";

// * api
import { getCoin } from "../services/api";

// *spinner
import Loader from "./Loader";

const Company = () => {
  const [selectedCoin, setSelectedCoin] = useState(null);
  const [coins, setCoins] = useState([]);

  useEffect(() => {
    const fetchAPI = async () => {
      const data = await getCoin();
      setCoins(data);
    };
    fetchAPI();
  }, []);

  const coinId = () => {
    console.log(selectedCoin);
  };

  coinId();

  return (
    <>
      {coins.length > 0 ? (
        coins.map((coin) => (
          <Row
            className={
              selectedCoin === coin.id
                ? "p-2 pe-3 border-top d-flex align-items-center company-list-single-active"
                : "p-2 border-top d-flex align-items-center company-list-single"
            }
            onClick={() => {
              setSelectedCoin(coin.id);
              // console.log(coin.id);
              // console.log(coin.name);
            }}
            key={coin.id}
          >
            <Col xxl="2" xl="2" lg="3" md="3" sm="2" xs="2">
              <Image
                src={coin.image}
                alt={coin.name}
                className="coin-image mx-2"
                fluid
              />
            </Col>
            <Col>
              <span>{coin.name}</span>
            </Col>
          </Row>
        ))
      ) : (
        <Loader />
      )}
    </>
  );
};
export default Company;

details components:

import React, { useState, useEffect } from "react";
import axios from "axios";

const Details = () => {
  const [data, setData] = useState({
    name: "",
    id: "",
  });

  const apiDetails = async () => {
    await axios
      .get(`https://api.coingecko.com/api/v3/coins/${"ethereum"}`)
      .then((r) => {
        // console.log(response);
        setData({
          name: r.data.name,
          id: r.data.id,
        });
        return setData;
      });
  };

  useEffect(() => {
    (async () => {
      const response = await apiDetails();
      setData({
        name: response.data.name,
        id: response.data.id,
      });
    })();
  }, []);

  return (
    <div>
      <h1>{data.name}</h1>
      <h1>{data.id}</h1>
    </div>
  );
};

export default Details;

2 Answers2

4

Well, looks like you're just interested on the code, right? so I'll just drop the code in here.

Always feel free to move code around and use the syntax that makes sense to you.


CoinsContext.js

import React, { createContext, useContext, useState, useEffect } from "react";
import { getCoin } from "@api";

const CoinsContext = createContext({});

export const CoinsContextProvider = ({ children }) => {
  const [selectedCoin, setSelectedCoin] = useState(null);
  const [coins, setCoins] = useState([]);

  useEffect(() => {
    const fetchAPI = async () => {
      const data = await getCoin();
      setCoins(data);
    };
    fetchAPI();
  }, []);

  return (
    <CoinsContext.Provider value={{ selectedCoin, setSelectedCoin, coins }}>
      {children}
    </CoinsContext.Provider>
  );
};

export const useCoins = () => useContext(CoinsContext);

If you want to use the above context on Detail.js and Company.js, you must wrap up the pages with it, that applies to all pages. Usually we wrap the entire App, which means the entire app can use it. There is however a down side. Every time a state inside a context updates, all the pages that are wrapped by that context will update as well, therefore you must assess your application and choose what's best for you.

That said, wrapping the whole app looks like this:

index.js

import React from "react";
import ReactDOM from "react-dom";
import App from "./App";

import { CoinsContextProvider } from "./CoinsContext";

ReactDOM.render(
  <React.StrictMode>
    <CoinsContextProvider>
      <App />
    </CoinsContextProvider>
  </React.StrictMode>,
  document.getElementById("root")
);

Now that we have CoinsContext set up, we can use it on your components.

Company.js

import React from "react";
import { Col, Image, Row } from "react-bootstrap";
import "./Company.scss";

// *spinner
import Loader from "./Loader";
import { useCoins } from "./CoinsContext";

const Company = () => {
  const { coins, selectedCoin, setSelectedCoin } = useCoins();

  return (
    <>
      {coins.length > 0 ? (
        coins.map((coin) => (
          <Row
            className={
              selectedCoin === coin.id
                ? "p-2 pe-3 border-top d-flex align-items-center company-list-single-active"
                : "p-2 border-top d-flex align-items-center company-list-single"
            }
            onClick={() => {
              setSelectedCoin(coin.id);
              // console.log(coin.id);
              // console.log(coin.name);
            }}
            key={coin.id}
          >
            <Col xxl="2" xl="2" lg="3" md="3" sm="2" xs="2">
              <Image
                src={coin.image}
                alt={coin.name}
                className="coin-image mx-2"
                fluid
              />
            </Col>
            <Col>
              <span>{coin.name}</span>
            </Col>
          </Row>
        ))
      ) : (
        <Loader />
      )}
    </>
  );
};
export default Company;

Details.js

import React, { useState, useEffect } from "react";
import axios from "axios";

import { useCoins } from "./CoinsContext";

const Details = () => {
  const { selectedCoin, coins, setSelectedCoin } = useCoins();
  
  const [data, setData] = useState({
    name: "",
    id: "",
  });

  // I recommend you to move this to the context as well
  const apiDetails = async () => {
    await axios
      .get(`https://api.coingecko.com/api/v3/coins/${"ethereum"}`)
      .then((r) => {
        // console.log(response);
        setData({
          name: r.data.name,
          id: r.data.id,
        });
        return setData;
      });
  };

  useEffect(() => {
    (async () => {
      const response = await apiDetails();
      setData({
        name: response.data.name,
        id: response.data.id,
      });
    })();
  }, []);

  return (
    <div>
      <h1>{data.name}</h1>
      <h1>{data.id}</h1>
    </div>
  );
};

export default Details;

And we're done! Now you are not just sharing a state (selectedCoin) between your components, but you're also centralizing the fetch coins business logic in your context, which overall is the right thing to do.

laian
  • 270
  • 2
  • 5
2

You just need to create a wrapper of your context. this wrapper is just a component, and its state will be passed as values of your context to allow you to edit it wherever you want.

Here is a simple example of how you can implement your context to share data between components :

import React, { Component } from 'react';
import { render } from 'react-dom';
import './style.css';

const MyContext = React.createContext();
const MyContextProvider = (props) => {
  const [data, setData] = React.useState('Initial value');

  return (
    <MyContext.Provider value={{ data, setData }}>
      {props.children}
    </MyContext.Provider>
  );
};

const App = () => {
  return (
    <MyContextProvider>
      <Component1 />
      <hr />
      <Component2 />
    </MyContextProvider>
  );
};

const Component1 = () => {
  const { data, setData } = React.useContext(MyContext);
  return (
    <>
      [component 1] data = {data}
      <br />
      <button onClick={() => setData('data written from component 1')}>
        Update context
      </button>
    </>
  );
};

const Component2 = () => {
  const { data, setData } = React.useContext(MyContext);
  return (
    <>
      [component 2] data = {data}
      <br />
      <button onClick={() => setData('data written from component 2')}>
        Update context
      </button>
    </>
  );
};

render(<App />, document.getElementById('root'));

Here is the Stackblitz.

Quentin Grisel
  • 4,794
  • 1
  • 10
  • 15