match.params.id not working in react router dom - reactjs

I have installed the latest version of react-router-dom in my project. And it is saying match is undefined. However, it works fine when I use react-router-dom#5.2.0.
Can anybody tell me how should i change my code to use the match.params.id or what is the substitute method in the latest react-router-dom??
Attached is my code which is working fine in react router dom version#5.2.0.
import React from 'react';
import { useState, useEffect } from 'react';
function ItemDetail({match}) {
const [item, setItem]= useState({});
const [isLoading, setIsLoading] = useState(false);
const [hasError, setHasError] = useState(false);
useEffect(()=>{
fetchItem();
//console.log(match);
// eslint-disable-next-line react-hooks/exhaustive-deps
},[setItem]);
const fetchItem= async ()=>{
setIsLoading(true);
setHasError(false);
try {
const fetchItem= await fetch(`https://fortnite-api.theapinetwork.com/item/get?id=${match.params.id}`
);
const item1 = await fetchItem.json();
setItem(item1.data.item);
console.log(item);
console.log(item1);
} catch (error) {
setHasError(true);
}
setIsLoading(false);
}
return (
<div >
<h1>{item.name}</h1>
</div>
);
}
export default ItemDetail;

First import { useParams } from 'react-router-dom';
add const {id} = useParams();
And instead of using (match.params.id) use only (id)
Hope this will work!

You can try to use hooks: useParams, that returns an object with all variables inside your route
const params = useParams();
if the problem persists then it may be a problem with your component hierarchy or route definition

Related

React Context API current

Okay...what is happening here cause I don't undrestand? I have an react context api where I store the current user data. In the login function I console log the currentUser and everything looks fine, but then when I write the currentUser in chrome dev tools, it appears undefined. In localStorage I also have the data. What is happening? What am I doing wrong? Can someone, please help me.
Here is my code:
authContext.js
import { createContext, useState, useEffect } from "react";
import axios from "axios";
export const AuthContext = createContext();
export const AuthContextProvider = ({ children }) => {
const [currentUser, setCurrentUser] = useState(
JSON.parse(localStorage.getItem("user")) || null
);
const login = async (inputs) => {
try {
const res = await axios.post("/login", inputs);
setCurrentUser(res.data);
console.log("res.data: ", res.data); //returns data
console.log("currentUser ", currentUser); //returns data
} catch (error) {
console.log(error);
}
};
const logout = async () => {
localStorage.clear();
setCurrentUser(null);
};
useEffect(() => {
localStorage.setItem("user", JSON.stringify(currentUser));
}, [currentUser]);
return (
<AuthContext.Provider value={{ currentUser, login, logout }}>
{children}
</AuthContext.Provider>
);
};
index.js
import React from "react";
import ReactDOM from "react-dom/client";
import { AuthContextProvider } from "./ccontext/authContext";
import App from "./App";
import "./index.css";
const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(
<React.StrictMode>
<AuthContextProvider>
<App />
</AuthContextProvider>
</React.StrictMode>
);
Login.jsx
/*...*/
const LoginForm = () => {
const [error, setError] = useState(null);
const navigate = useNavigate();
const { login } = useContext(AuthContext);
const handleFormSubmit = (values, actions) => {
try {
login(values);
actions.resetForm();
navigate("/");
console.log("logged in");
} catch (err) {
setError(err.response.data);
}
};
/*...*/
Updating state is best seen like an asynchronous operation. You cannot set state in a function / effect and expect it to immediately be updated, on the spot. Well, it technically is, but you won't see the updates in your "already-running" function.
I am pretty sure that if you extract your log in the component root it will display the appropriate value after the login function finishes executing and the component properly sets the new state.
If you do need the value in the function you should directly use res.data.
A deeper dive:
Whenever your login function runs it forms a closure around your current values (including currentUser which is undefined at the moment).
When you update the currentUser in the login function you basically inform react that you need to update that value. It will handle this in the background, preparing the state for the next render, but your login function will keep running with whatever values it started with. Your "new" state values will not be available until you run the function again. This is because the already-running function "closed over" old values, so it can only reference those.
As a side note, if you use a ref for instance you would not have this problem. Why? Because refs do not participate in the react lifecycle. When you modify a ref it changes on the spot. You will have the updated value precisely on the next line. State does not work like that, it is coupled to the component lifecycle, so it will update on the next render.

Why is the `question` property in the API not reachable and why is questionBank not rendering?

Just to let you know, I don't yet know how to use class based components, setState, etc. I also don't know how to use other things in async js like axios or whatever else yet. This is what I can do below. Very basic.
This is App.js:
import Questions from './components/Questions.js'
import './index.css'
import {React, useState, useEffect} from 'react'
function App() {
const [questions, setQuestions] = useState([])
useEffect(() => {
async function getQuestions(){
const response = await fetch("https://opentdb.com/api.php?amount=5")
const data = await response.json()
setQuestions(() => data.results)
}
getQuestions()
}, [])
const questionBank = questions.map(singleQuestion => {
<Questions
question={singleQuestion.question}
/>
})
console.log(questions[0].question)
return (
<div>
{questionBank}
</div>
);
}
export default App;
For some reason, console.log(questions[0].question) when typed in to the editor and saved for the first time, it shows a question from the api. But after refreshing the page it doesn't show a question but it says: App.js:44 Uncaught TypeError: Cannot read properties of undefined (reading 'question') But when I just do this: console.log(questions[0]), it shows the first object of the array from the API no problem. I'm confused.
Also, questionBank doesn't render at all for some reason.
This is Questions.js:
import React from 'react'
export default function Questions(props){
return(
<div>
<p>{props.question}</p>
<br />
</div>
)
}
This is index.js:
import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App';
const root = ReactDOM.createRoot(document.getElementById("root"))
root.render(
<App />
)
There are few things to improve in your code (such as maybe make a useCallback out of function getQuestions in the useEffect), but the biggest issue is that you are not properly returning JSX from the map method.
Your code:
const questionBank = questions.map(singleQuestion => {
<Questions
question={singleQuestion.question}
/>
})
notice the curly braces { & }. The proper code:
const questionBank = questions.map(singleQuestion => (
<Questions
question={singleQuestion.question}
/>
))
After this change, your code should work properly.
Also, your console.log will cause errors, because you are console.logging before even fetching theses questions, so obviously questions[0] is undefined.
More improvements to the code:
export default function App() {
const [questions, setQuestions] = useState([]);
const getQuestions = useCallback(async () => {
const response = await fetch("https://opentdb.com/api.php?amount=5");
const data = await response.json();
setQuestions(data.results);
}, []);
useEffect(() => {
getQuestions();
}, [getQuestions]);
const questionBank = questions.map((singleQuestion) => (
<Questions question={singleQuestion.question} />
));
return <div>{questionBank}</div>;
}

react js undefined is not iterable (cannot read property Symbol(Symbol.iterator)) error

so i am trying to make a to-do app with react and i just tried using Context and i keep getting undefined is not iterable:
this is Main/index.js:
import React,{useContext} from "react";
import { BrowserRouter as Router,Routes, Route, Link} from 'react-router-dom';
import {GlobalContext} from "../Context/provider.js"
import TodoForm from "../Todo-Form/index.js"
import Description from "../Description/index.js"
import "./style.css";
export default function Main () {
const {todos, setTodos} = useContext(GlobalContext);
const addTodo = text => {
const newTodos = [...todos, { text }];
setTodos(newTodos);
};
const completeTodo = index => {
const newTodos = [...todos];
newTodos[index].isCompleted = newTodos[index].isCompleted ? false : true;
setTodos(newTodos);
};
this is Context/provider.js:
import React from "react";
import { useState,createContext } from "react";
export const GlobalContext = createContext()
export const ContextProvider = ({children})=> {
const [todos, setTodos] = useState([
{
text: "You can write your to-do above and add",
isCompleted: false,
description: "hello",
id:0
},
]);
return(
<GlobalContext.Provider value = {{todos, setTodos}} >
{children}
</GlobalContext.Provider>
)
}
EDİT SOLVED !!
aside from my problem with:
const {todos, setTodos} = useContext(GlobalContext);
it turns out i havent wrapped with provider so when i did this in app.js it worked :
<ContextProvider>
<div className="main-div">
<Header />
<Main />
</div>
</ContextProvider>
thanks for everyone who took a look and provided some solutions.
The problem might be here: const [todos, setTodos] = useContext({GlobalContext});
Try putting const [todos, setTodos] = useContext(GlobalContext); (removing the brackets around GlobalContext) because you're passing an object with {GlobalContext: GlobalContext} instead of just passing the context as an argument.
In addition to this, there are couple of more issues that I see:
The main one is that you need to wrap <Main /> with that you define in provider.js. Then you can put components within ContextProvider that can access the context.
Fix the spelling error in the children prop in ContextProvider
Here's a sandbox: https://codesandbox.io/s/shy-meadow-8gf0sd?file=/src/App.js
The problem is that you are spearing an object inside an array. If your global context is an array you should set the value as follow:
const [todos, setTodos] = useContext(GlobalContext);
After that, you can iterate through todos.
In addition to this, there are couple of more issues that I see:
The main is that you need to wrap with that you import from provider.js. Then you can put components within ContextProvider that can access the context.
fix the spelling error in the children prop in ContextProvider
Here's a sandbox: https://codesandbox.io/s/shy-meadow-8gf0sd?file=/src/App.js

React router v6 history.listen

In React Router v5 there was a listen mehtode on the history object.
With the method I wrote a usePathname hook to rerender a component on a path change.
In React Router v6, this method no longer exists. Is there an alternative for something like this? I would hate to use useLocation because it also renders if the state changes, which I don't need in this case.
The hook is used with v5.
import React from "react";
import { useHistory } from "react-router";
export function usePathname(): string {
let [state, setState] = React.useState<string>(window.location.pathname);
const history = useHistory();
React.useLayoutEffect(
() =>
history.listen((locationListener) => setState(locationListener.pathname)),
[history]
);
return state;
}
As mentioned above, useLocation can be used to perform side effects whenever the current location changes.
Here's a simple typescript implementation of my location change "listener" hook. Should help you get the result you're looking for
function useLocationEffect(callback: (location?: Location) => any) {
const location = useLocation();
useEffect(() => {
callback(location);
}, [location, callback]);
}
// usage:
useLocationEffect((location: Location) =>
console.log('changed to ' + location.pathname));
I am using now this code
import { BrowserHistory } from "history";
import React, { useContext } from "react";
import { UNSAFE_NavigationContext } from "react-router-dom";
export default function usePathname(): string {
let [state, setState] = React.useState<string>(window.location.pathname);
const navigation = useContext(UNSAFE_NavigationContext)
.navigator as BrowserHistory;
React.useLayoutEffect(() => {
if (navigation) {
navigation.listen((locationListener) =>
setState(locationListener.location.pathname)
);
}
}, [navigation]);
return state;
}
It seems to work fine
I find using useNavigate and useLocation quite meaningless compared to useHistory in React Rrouter v5.
As a result of these changes, I made a thin custom hook to ease myself from any refactoring.
Just rename the import path to this hook and use the "old" api with v6. To answer or just give hints to your question - using this approach is should be easy to implement the listen function in the custom hook yourself.
export function useHistory() {
const navigate = useNavigate();
const location = useLocation();
const listen = ...; // implement the hook yourself
return {
push: navigate,
go: navigate,
goBack: () => navigate(-1),
goForward: () => navigate(1),
listen,
location,
};
}
Why not simply use const { pathname } = useLocation();? It will indeed renders if the state changes but it shouldn't be a big deal in most scenarii.
If you REALLY want to avoid such behaviour, you could create a context of your own to hold the pathname:
// PathnameProvider.js
import React, { createContext, useContext } from 'react';
import { useLocation } from 'react-router';
const PathnameContext = createContext();
const PathnameProvider = ({ children }) => {
const { pathname } = useLocation();
return (
<PathnameContext.Provider value={pathname}>
{children}
</PathnameContext.Provider>
);
}
const usePathname = () => useContext(PathnameContext);
export { PathnameProvider as default, usePathname };
Then you can use usePathname() in any component down the tree. It will render only if the pathname actually changed.
Given that #kryštof-Řeháček's recommendation (just above) is to implement your own useListen hook, but it might not be obvious how to do that, here's a version I've implemented for myself as a guide (nb: I havent't exhaustively unit tested this yet):
import { useState } from "react";
import { useLocation } from "react-router";
interface HistoryProps {
index: number;
isHistoricRoute: boolean;
key: string;
previousKey: string | null;
}
export const useHistory = (): HistoryProps => {
const { key } = useLocation();
const [history, setHistory] = useState<string[]>([]);
const [currentKey, setCurrentKey] = useState<string | null>(null);
const [previousKey, setPreviousKey] = useState<string | null>(null);
const contemporaneousHistory = history.includes(key)
? history
: [...history, key];
const index = contemporaneousHistory.indexOf(key);
const isHistoricRoute = index + 1 < contemporaneousHistory.length;
const state = { index, isHistoricRoute, key, previousKey };
if (history !== contemporaneousHistory) setHistory(contemporaneousHistory);
if (key !== currentKey) {
setPreviousKey(currentKey);
setCurrentKey(key);
}
return state;
}
I now have just created a new routing library for react where this is possible.
https://github.com/fast-router/fast-router
Server-Side rendering is not supported. The rest should work fine. The library is mainly inspired by wouter -> https://github.com/molefrog/wouter
There are hooks for example usePathname which only cause a new render if the actual pathname changes (ignoring the hash and search)
It is possible to select just a single property of the history.state and don't get a new render if any other values inside the state changes.

How to remove query param with react hooks?

I know we can replace query params in component based classes doing something along the lines of:
componentDidMount() {
const { location, replace } = this.props;
const queryParams = new URLSearchParams(location.search);
if (queryParams.has('error')) {
this.setError(
'There was a problem.'
);
queryParams.delete('error');
replace({
search: queryParams.toString(),
});
}
}
Is there a way to do it with react hooks in a functional component?
For React Router V6 and above, see the answer below.
Original Answer:
Yes, you can use useHistory & useLocation hooks from react-router:
import React, { useState, useEffect } from 'react'
import { useHistory, useLocation } from 'react-router-dom'
export default function Foo() {
const [error, setError] = useState('')
const location = useLocation()
const history = useHistory()
useEffect(() => {
const queryParams = new URLSearchParams(location.search)
if (queryParams.has('error')) {
setError('There was a problem.')
queryParams.delete('error')
history.replace({
search: queryParams.toString(),
})
}
}, [])
return (
<>Component</>
)
}
As useHistory() returns history object which has replace function which can be used to replace the current entry on the history stack.
And useLocation() returns location object which has search property containing the URL query string e.g. ?error=occurred&foo=bar" which can be converted into object using URLSearchParams API (which is not supported in IE).
Use useSearchParams hook.
import {useSearchParams} from 'react-router-dom';
export const App =() => {
const [searchParams, setSearchParams] = useSearchParams();
const removeErrorParam = () => {
if (searchParams.has('error')) {
searchParams.delete('error');
setSearchParams(searchParams);
}
}
return <button onClick={removeErrorParam}>Remove error param</button>
}

Resources