Reputation: 1925
I'm using the context API and I have this in my context file:
useEffect(() => {
async function getSingleCountryData() {
const result = await axios(
`https://restcountries.eu/rest/v2/alpha/${props.match.alpha3Code.toLowerCase()}`
);
console.log(result)
setCountry(result.data);
}
if (props.match) getSingleCountryData();
}, [props.match]);
In the component I'm using, it doesn't work because it doesn't know what the props.match.alpha3Code is. How can I can pass the value? The alpha3Code is coming from the URL: localhost:3000/country/asa
where asa
is the alpha3Code, how can I get this value?
Basically, what I'm trying to do is. I have a list of countries I listed out on the home page. Now I'm trying to get more information about a single country. The route is /country/:alpha3Code
where alpha3Code
is gotten from the API.
FWIW, here is my full context file:
import React, { useState, createContext, useEffect } from 'react';
import axios from 'axios';
export const CountryContext = createContext();
export default function CountryContextProvider(props) {
const [countries, setCountries] = useState([]);
const [country, setCountry] = useState([]);
useEffect(() => {
const getCountryData = async () => {
const result = await axios.get(
'https://cors-anywhere.herokuapp.com/https://restcountries.eu/rest/v2/all'
);
setCountries(result.data);
};
getCountryData();
}, []);
useEffect(() => {
async function getSingleCountryData() {
const result = await axios(
`https://restcountries.eu/rest/v2/alpha/${props.match.alpha3Code.toLowerCase()}`
);
console.log(result)
setCountry(result.data);
}
if (props.match) getSingleCountryData();
}, [props.match]);
return (
<CountryContext.Provider value={{ countries, country }}>
{props.children}
</CountryContext.Provider>
);
}
In the component I'm using the country
, I have:
const { country } = useContext(CountryContext);
I know I can do this from the component itself, but I'm learning how to use the context API, so I'm handling all API calls in my context.
The API I'm making use of is here
Project Github link
Upvotes: 4
Views: 10940
Reputation: 1650
You can update the context from a component using it by passing down a setter function which updates the context state.
export default function CountryContextProvider({ children }) {
const [countries, setCountries] = useState([]);
const [country, setCountry] = useState([]);
const [path, setPath] = useState('');
useEffect(() => {
async function getSingleCountryData() {
const result = await axios(`your/request/for/${path}`);
setCountry(result.data);
}
if(path) getSingleCountryData();
}, [path]);
return (
<CountryContext.Provider value={{ countries, country, setPath }}>
{children}
</CountryContext.Provider>
);
}
Now use setPath
to update the request endpoint with the route match once this component is mounted.
const Details = ({ match }) => {
const {
params: { alpha3Code }
} = match;
const { country, setPath } = useContext(CountryContext);
useEffect(() => {
setPath(alpha3Code);
}, [alpha3Code]);
return (
<main>Some JSX here</main>
);
};
export default withRouter(Details);
Linked is a working codesandbox implementation
Upvotes: 6
Reputation: 492
In the component I'm using, it doesn't work because it doesn't know what the props.match.alpha3Code is. How can I can pass the value? The alpha3Code is coming from the URL: localhost:3000/country/asa where asa is the alpha3Code, how can I get this value?
I guess the root of your problem is this one. You have no idea which the aplha3Code
parameter comes from. I have dived into your GitHub repo to make it clearer.
match
is one of react-router provided terms. When you use something like props.match, props.history, props.location
, you must have your component wrapped by the withRouter
, which is a Higher Order Component provided by react-router
. Check it out at withRouter. For example, below is the withRouter
usage which is provided by react-router:// A simple component that shows the pathname of the current location
class ShowTheLocation extends React.Component {
render() {
const { match, location, history } = this.props;
return <div>You are now at {location.pathname}</div>;
}
}
const ShowTheLocationWithRouter = withRouter(ShowTheLocation);
ShowTheLocation
is wrapped by the withRouter
HOC, which will pass all the route props (match, history, location...) to ShowTheLocation
through props. Then inside ShowTheLocation
, you are able to use something like props.match
. Clear enough?
So back to your problem! You have not wrapped any components by withRouter
yet, have you? Stick to it and have some fun! You will figure it out soon!
Also, please be aware that you must place your component under the BrowserRouter
to be able to use the react-router things
If you want to go with Hooks, please take a look at this super useful one:
It wraps all the useParams, useLocation, useHistory, and use useRouteMatch
hooks up into a single useRouter
that exposes just the data and methods we need. Then, for example, inside your component, do it like this:
import { useRouter } from "./myCustomHooks";
const ShowMeTheCode = () => {
const router = useRouter();
return <div>This is my alpha3Code: {router.math.params.alpha3Code}</div>;
}
Update 1 from Peoray's reply:
This is where the problem occurs: https://github.com/peoray/where-in-the-world/blob/cb09871fefb2f58f5cf0a4f1db3db2cc5227dfbe/src/pages/Details.js#L6
You should avoid calling useContext()
straightly like that. Have a look at my example below:
// CountryContext.js
import { useContext, createContext } from "react";
const CountryContext = createContext();
export const useCountryContext = () => useContext(CountryContext);
Instead, you should wrap it by a custom hook like useCountryContext
above. And then, inside your Details
component, import it and do like:
import React, from 'react';
import { useCountryContext } from '../contexts/CountryContext';
const Details = (props) => {
const { country } = useCountryContext();
...
}
Update 2 from Peoray's reply:
Although I have stated it in advance for you, I just feel like you did not make enough effort to go through what I said.
Also, please be aware that you must place your component under the
BrowserRouter
to be able to use the react-router things
In your codesandbox, it shows the Cannot read property 'match' of undefined
error. Okay, as I said above, you have not moved the ContextCountryProvider
to under the BrowserRouter
to get the useRouter
work.
I have fixed it for you, and the screen popped out, please check it at updated codesanbox here. You will get what you need at App.js file.
Although it still throws some Axios bugs there, I think my job is done. The rest is up to you.
Upvotes: 3
Reputation: 1864
You might use useParams
hook to get everything you need inside your context provider. Docs
Something like this:
useParams
in file where your Provider component isCountryContextProvider
add this at the top of the component:const { alpha3Code } = useParams();
useEffect
which needs props.match
useEffect(() => {
async function getSingleCountryData() {
const result = await axios(
`https://restcountries.eu/rest/v2/alpha/${alpha3Code.toLowerCase()}`
);
console.log(result)
setCountry(result.data);
}
if (alpha3Code) getSingleCountryData(); // or if you need `match` - do not destructure useParams()
}, [alpha3Code]);
Upvotes: 0