I've set up my context and I have a function that runs once the form is submitted handleSubmit. When I submit the form, I want the results to be shown on a separate page dashboard. I'm using history.push().
My form is wrapped in the withRouter HOC.
When I submit the form, I receive "props.history is undefined"
I also have another function that is using a match.params and I'm getting undefined as well. So I'm assuming it has to do with React Router.
I considered that perhaps my Context file is the one that needs to be wrapped with the withRouter HOC, but the file has two exports.
My Context Provider
import React, { useState, useEffect, createContext } from 'react'
const AnimeContext = createContext()
const API = "https://api.jikan.moe/v3"
const AnimeProvider = (props) => {
const urls = [
`${API}/top/anime/1/airing`,
`${API}/top/anime/1/tv`,
`${API}/top/anime/1/upcoming`,
]
// State for Anime search form
const [dataItems, setDataItems] = useState([])
const [animeSearched, setAnimeSearched] = useState(false)
// Fetch searched Anime
async function handleSubmit(e) {
e.preventDefault()
const animeQuery = e.target.elements.anime.value
const response = await fetch(`${API}/search/anime?q=${animeQuery}&page=1`)
const animeData = await response.json()
setDataItems(animeData.results)
setAnimeSearched(!animeSearched)
props.history.push('/dashboard')
}
return (
<AnimeContext.Provider value={{
topTv,
setTopTv,
topAiring,
setTopAiring,
topUpcoming,
setTopUpcoming,
dataItems,
setDataItems,
animeSearched,
setAnimeSearched,
fetching,
anime,
fetchTopAnime,
fetchAnimeDetails,
handleSubmit
}}>
{props.children}
</AnimeContext.Provider>
)
}
export { AnimeProvider, AnimeContext }
My SearchForm component
import React, { useContext } from 'react';
import { withRouter } from 'react-router-dom'
import styled from 'styled-components'
import AnimeCard from './AnimeCard/AnimeCard';
import { AnimeContext } from '../store/AnimeContext'
const SearchForm = () => {
const { dataItems, animeSearched, handleSubmit } = useContext(AnimeContext)
return (
<div>
<Form onSubmit={handleSubmit}>
<Input
type="text"
name="anime"
placeholder="Enter title"
/>
<FormButton type='submit'>Search</FormButton>
</ Form>
{animeSearched
?
<AnimeCard
dataItems={dataItems}
/>
: null}
</div>
)
}
export default withRouter(SearchForm)
you can always use useHitory hook everywhere!
import { useHistory } from 'react-router'
...
const Page = function(props) {
let history = useHistory();
...
history.push('/')
...
}
In react-router, you would get history from props if any component is rendered as a child or Route or from an ancestor that is renderd form Route and it passed the Router props to it. However it is not receiving Router props, i suggest try this one
You can use Redirect from react-router-dom
import { Redirect } from "react-router-dom";
const [redirect, setRedirect] = useState(false);
Now set the vlue of redirect to true where ever you want
setRedirect(true);
like in your case
async function handleSubmit(e) {
e.preventDefault()
const animeQuery = e.target.elements.anime.value
const response = await fetch(`${API}/search/anime?q=${animeQuery}&page=1`)
const animeData = await response.json()
setDataItems(animeData.results)
setAnimeSearched(!animeSearched)
setRedirect(true);
}
Now you can use the following for the Redirection in return function like so
if(redirect) {
return <Redirect to="/dashboard" />
} else {
return (
<Your-Component />
)
Related
I'm currently making a simple web frontend with react using react-autosuggest to search a specified user from a list. I want to try and use the Autosuggest to give suggestion when the user's type in the query in the search field; the suggestion will be based on username of github profiles taken from github user API.
What I want to do is to separate the AutoSuggest.jsx and then import it into Main.jsx then render the Main.jsx in App.js, however it keeps giving me 'TypeError: _ref2 is undefined' and always refer to my onChange function of AutoSuggest.jsx as the problem.
Below is my App.js code:
import './App.css';
import 'bootstrap/dist/css/bootstrap.min.css';
import Header from './views/header/Header';
import Main from './views/main/Main';
import Footer from './views/footer/Footer';
const App = () => {
return (
<>
<Header/>
<Main/> <- the autosuggest is imported in here
<Footer/>
</>
);
}
export default App;
Below is my Main.jsx code:
import React, { useState } from 'react';
import Container from 'react-bootstrap/Container';
import Row from 'react-bootstrap/Row';
import axios from 'axios';
import { useEffect } from 'react';
import AutoSuggest from '../../components/AutoSuggest';
const Main = () => {
const [userList, setUserList] = useState([]);
useEffect(() => {
axios.get('https://api.github.com/users?per_page=100')
.then((res) => setUserList(res.data))
.catch((err) => console.log(err));
}, [])
return (
<Container>
<br/>
<Row>
<AutoSuggest userList={userList} placeHolderText={'wow'} />
</Row>
</Container>
);
}
export default Main;
Below is my AutoSuggest.jsx code:
import React, { useState } from "react";
import Autosuggest from 'react-autosuggest';
function escapeRegexCharacters(str) {
return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
}
function getSuggestions(value, userList) {
const escapedValue = escapeRegexCharacters(value.trim());
if (escapedValue === '') {
return [];
}
const regex = new RegExp('^' + escapedValue, 'i');
return userList.filter(user => regex.test(user.login));
}
function getSuggestionValue(suggestion) {
return suggestion.name;
}
function renderSuggestion(suggestion) {
return (
<span>{suggestion.name}</span>
);
}
const AutoSuggest = ({userList, placeHolderText}) => {
const [value, setValue] = useState('');
const [suggestions, setSuggestions] = useState([]);
const onChange = (event, { newValue, method }) => { <- error from console always refer here, I'm not quite sure how to handle it..
setValue(newValue);
};
const onSuggestionsFetchRequested = ({ value }) => {
setValue(getSuggestions(value, userList))
};
const onSuggestionsClearRequested = () => {
setSuggestions([]);
};
const inputProps = {
placeholder: placeHolderText,
value,
onChange: () => onChange()
};
return (
<Autosuggest
suggestions={suggestions}
onSuggestionsFetchRequested={() => onSuggestionsFetchRequested()}
onSuggestionsClearRequested={() => onSuggestionsClearRequested()}
getSuggestionValue={() => getSuggestionValue()}
renderSuggestion={() => renderSuggestion()}
inputProps={inputProps} />
);
}
export default AutoSuggest;
The error on browser (Firefox) console:
I have no idea what does the error mean or how it happened and therefore unable to do any workaround.. I also want to ask if what I do here is already considered a good practice or not and maybe some inputs on what I can improve as well to make my code cleaner and web faster. Any input is highly appreciated, thank you in advance!
you have to write it like this... do not use the arrow function in inputProps
onChange: onChange
I am facing a problem with re-rendering after a state change in my NextJS app.
The function sendMessageForm launches a redux action sendMessage which adds the message to the state.
The problem is unrelated to the returned state in the reducer as I am returning a new object(return {...state}) which should trigger the re-render!
Is there anything that might block the re-render ?
This is the file that calls & displays the state, so no other file should be responsible ! But if you believe the problem might lie somewhere else, please do mention !
import { AttachFile, InsertEmoticon, Mic, MoreVert } from '#mui/icons-material';
import { Avatar, CircularProgress, IconButton } from '#mui/material';
import InfiniteScroll from 'react-infinite-scroller';
import Head from 'next/head';
import { useState, useEffect } from 'react';
import Message from '../../components/Message.component';
import styles from '../../styles/Chat.module.css'
import { useRouter } from 'next/router'
import {useSelector, useDispatch} from "react-redux"
import {bindActionCreators} from "redux"
import * as chatActions from "../../state/action-creators/chatActions"
const Chat = () => {
const router = useRouter()
const { roomId } = router.query
const auth = useSelector((state)=> state.auth)
const messages = useSelector((state)=> state.chat[roomId].messages)
const dispatch = useDispatch()
const {getMessages, markAsRead, sendMessage} = bindActionCreators(chatActions, dispatch)
const [inputValue, setInputValue] = useState("")
const sendMessageForm = (e) => {
e.preventDefault()
console.log("***inputValue:", inputValue)
sendMessage(roomId, inputValue)
}
const loadMessages = (page) => {
if(roomId)
getMessages(roomId, page)
}
//user-read-message
useEffect(() => {
//user-read-message
markAsRead(roomId, auth.user._id)
}, [messages]);
return (
<div className={styles.container}>
<Head>
<title>Chat</title>
</Head>
<div className={styles.header}>
<Avatar/>
<div className={styles.headerInformation}>
<h3>Zabre el Ayr</h3>
<p>Last Seen ...</p>
</div>
<div className={styles.headerIcons}>
<IconButton>
<AttachFile/>
</IconButton>
<IconButton>
<MoreVert/>
</IconButton>
</div>
</div>
<div className={styles.chatContainer}>
<InfiniteScroll
isReverse={true}
pageStart={0}
loadMore={loadMessages}
hasMore={messages.hasNextPage || false}
loader={<div className={styles.loader} key={0}><CircularProgress /></div>}
>
{Object.keys(messages.docs).map((key, index)=>{
return<Message
key={index}
sentByMe={messages.docs[key].createdBy === auth.user._id}
message={messages.docs[key].msg}
/>})}
</InfiniteScroll>
<span className={styles.chatContainerEnd}></span>
</div>
<form className={styles.inputContainer}>
<InsertEmoticon/>
<input className={styles.chatInput} value={inputValue} onChange={(e)=>setInputValue(e.target.value)}/>
<button hidden disabled={!inputValue} type='submit' onClick={sendMessageForm}></button>
<Mic/>
</form>
</div>)
};
export default Chat;
useSelector requires a new object with a new reference from the object you are passing to it in order to trigger the re-render
What you're doing with return {...state} is just creating a new object for the parent object but not the nested one useSelector is using, which is in your case :
const messages = useSelector((state)=> state.chat[roomId].messages)
So, you should return the whole state as a new object WITH a new state.chat[roomId].messages object
In other words, the references for the root object & the one being used should be changed.
I am not able to retrieve content from API every time I reload my page it shows error, please see the attached image, I wanted to find the weather details using Weather API and right now I am using static latitude and longitude.
import React, { useState, useEffect } from "react";
import axios from "axios";
import { FaRegSun } from "react-icons/fa";
import "./stylesheets/stylesheets.css";
function WeatherApp1() {
const [weatherData2, setWeatherData2] = useState({});
const API_endpoint2 = `https://api.openweathermap.org/data/2.5/onecall?`;
const API_key = `2a63c27d8ba0b0d14c9e5d59f39ee1ba`;
useEffect(() => {
async function getSecondObject() {
const response = await axios.get(
`${API_endpoint2}lat=28.4360704&lon=77.021184&units=metric&appid=${API_key}`
);
setWeatherData2(response.data);
}
getSecondObject();
}, []);
return (
<div className="mainDiv">
<div className="heading">
<h1>
<FaRegSun /> Weather
</h1>
</div>
{weatherData2.current.temp}
</div>
);
}
export default WeatherApp1;
https://i.stack.imgur.com/oqr7i.jpg
The problem with your code is that you're trying to render {weatherData2.current.temp} before the data is returned from the weather API and that's why your weatherData2 will be undefined while rendering.
You can add a loading state for checking if the data is rendering or already rendered.
You can try below code:
import React, { useState, useEffect } from "react";
import axios from "axios";
import { FaRegSun } from "react-icons/fa";
import "./stylesheets/stylesheets.css";
function WeatherApp1() {
const [loading, setLoading] = useState(true) // Loading state
const [weatherData2, setWeatherData2] = useState({});
const API_endpoint2 = `https://api.openweathermap.org/data/2.5/onecall?`;
const API_key = `2a63c27d8ba0b0d14c9e5d59f39ee1ba`;
useEffect(() => {
async function getSecondObject() {
const response = await axios.get(
`${API_endpoint2}lat=28.4360704&lon=77.021184&units=metric&appid=${API_key}`
);
setWeatherData2(response.data);
setLoading(false) // Setting the loading state to false after data is set.
}
getSecondObject();
}, []);
return (
<div className="mainDiv">
<div className="heading">
<h1>
<FaRegSun /> Weather
</h1>
</div>
{/* Checking for loading state before rendering the data */}
{loading ? (
<p>Loading...</p>
) : (
weatherData2.current.temp
)}
</div>
);
}
export default WeatherApp1;
This question already has an answer here:
How do you pass data when using the navigate function in react router v6
(1 answer)
Closed last year.
I'm trying to redirect to another page with passing data after submitting form in using react-router-dom v6. When I will click on submit, data will be submited and the application will take me to the "Download PDF" page, also the form data will pass registration page to the "Download PDF" page.
Example Code:
ConfirmRegistration.js
import React, { useState } from "react";
import { Navigate } from "react-router-dom";
const ConfirmRegistration = () => {
const [name, setName] = useState();
const [confirm, setConfirm] = useState(false);
const handleOnChange = (e) => {
e.preventDefault();
const value = e.target.value;
setName(value);
};
const handleSubmit = (e) => {
e.preventDefault();
setConfirm(true);
console.log(name);
};
if (confirm) {
return (
<Navigate
to={{
pathname: "/download-pdf",
}}
/>
);
}
return (
<div>
<form onSubmit={handleSubmit}>
<input
type='text'
name='name'
placeholder='input text here'
onChange={handleOnChange}
/>
<button type='submit'>Submit</button>
</form>
</div>
);
};
export default ConfirmRegistration;
DownLoadPdf.js
import React from "react";
const DownLoadPdf = () => {
return (
<div>
<p>DownLoad Pdf</p>
</div>
);
};
export default DownLoadPdf;
You can use useNavigate instead of using Navigate
import React, { useState } from "react";
import { useNavigate } from "react-router-dom";
const ConfirmRegistration = () => {
const [name, setName] = useState();
const [confirm, setConfirm] = useState(false);
const navigate = useNavigate();
const handleOnChange = (e) => {
e.preventDefault();
const value = e.target.value;
setName(value);
};
const handleSubmit = (e) => {
e.preventDefault();
setConfirm(true);
console.log(name);
navigate('/download-pdf', {state:// Your data})
};
You can Use Hook Provided By React Router
import { useNavigate } from "react-router-dom";
const confirmRegistration = () => {
const navigate = useNavigate();
const handleSubmit = (e) => {
...
navigate('/route', {state: {//pdfData}})
};
}
Other Way : You can store Data in a Global State and use from there. Redux, ContextAPI etc
You're trying to pass data between components. There are several ways as using "Redux" state management, "Context API" and etc. Then in DownLoadPdf component you manipulate the data.
If the project is high-scale, prefer using a statemanagement like "Redux".
But you can simply pass data by navigate as this:
navigate('/download-pdf', {state: // The Data});
I've got component that displays contact information from a dealer as chosen by a user. To be more specific, a user selects their location, setting a cookie which then is used to define the API call. I pull in the contact information of the dealer in that location using Axios, store it in a context, and then display the information as necessary through several components: the header, a "current location" component etc.
The problem that I'm currently running into is that the contact information, as displayed in the Header for example, doesn't update until a user performs a hard refresh of the page, so, assuming the default text of the button is something like "Find A Dealer", once a dealer is selected, the button label should say the name of the dealer the user has selected. At present, it isn't working that way. Below is the code for the Header component, and my ApiContext.
ApiContext.tsx
import React, { createContext } from 'react';
import axios from 'axios';
import { makeUseAxios } from 'axios-hooks';
import { useCookie } from 'hooks/use-cookie';
const contextObject = {} as any;
export const context = createContext(contextObject);
const useAxios = makeUseAxios({
axios: axios.create({ baseURL: process.env.GATSBY_API_ENDPOINT }),
});
export const ApiContext = ({ children }: any) => {
const [cookie] = useCookie('one-day-location', '1');
const [{ data }] = useAxios(`${cookie}`);
const { Provider } = context;
return <Provider value={data}>{children}</Provider>;
};
Header.tsx
import React, { ReactNode, useContext, useEffect, useState } from 'react';
import Logo from 'assets/svg/logo.svg';
import css from 'classnames';
import { Button } from 'components/button/Button';
import { Link } from 'components/link/Link';
import { MenuIcon } from 'components/menu-icon/MenuIcon';
import { context } from 'contexts/ApiContext';
import { NotificationBar } from '../notification-bar/NotificationBar';
import s from './Header.scss';
import { MainNav } from './navigation/MainNav';
interface HeaderProps {
navigationContent: ReactNode;
}
export const Header = ({ navigationContent }: HeaderProps) => {
const [scrolled, setScrolled] = useState(false);
const [open, setOpen] = useState(false);
const data = useContext(context);
const buttonLabel = data ? data.name : 'Find a Dealer';
const buttonLink = data ? `tel:${data.phone}` : '/find-a-dealer';
useEffect(() => {
const handleScroll = () => {
const isScrolled = window.scrollY > 10;
if (isScrolled !== scrolled) {
setScrolled(!scrolled);
}
};
document.addEventListener('scroll', handleScroll, { passive: true });
return () => {
document.removeEventListener('scroll', handleScroll);
};
}, [scrolled]);
return (
<>
<NotificationBar notificationContent={navigationContent} />
<header className={scrolled ? css(s.header, s.header__scrolled) : s.header}>
<nav className={s.header__navigation}>
<ul className={s.header__container}>
<li className={s.header__logo}>
<Link to="/" className={s.header__link}>
<Logo />
</Link>
</li>
<li className={s.header__primary}>
<MainNav navigationItems={navigationContent} />
</li>
<li className={s.header__utility}>
<Button href={buttonLink}>{buttonLabel}</Button>
</li>
<li className={s.header__icon}>
<MenuIcon onClick={() => setOpen(!open)} />
</li>
</ul>
</nav>
</header>
</>
);
};
Here is a screenshot of my console logs, where I'm logging what is returned from data in the ApiContext.
Any suggestions on this would be greatly appreciated, even if it means completely refactoring the way that I'm using this. Thanks!
You are almost there, your ApiContext looks good, it retrieves the information and populates the context, however, what you are missing is a useState to trigger an update to force the re-hydration of your buttons.
What is happening is that your context never updates the data constant. At the first rendering is empty, once your request is done and the context is full but your button is never being updated. Something like this may work for you:
const data = useContext(context);
const [newData, setNewData] = useState(data);
const buttonLabel = newData? newData.name : 'Find a Dealer';
const buttonLink = newData? `tel:${newData.phone}` : '/find-a-dealer';
You may need to adapt the code a bit to fit your requirements, nevertheless, you may keep the idea, which is creating a state with your retrieved data.
You can create a useEffect to control when the data changes and populate the state if you wish:
useEffect(()=>{
setNewData(data)
}, [data])
After a lot of digging, I was able to figure this out myself.
Using the recommendations from Ferran as a base, I decided that it would be best to rehydrate the components displaying the contact info from a state, but as I'm using this context in multiple components, I needed to have the state update globally. I moved away from makeUseAxios, to a traditional axios call. The dealer ID is then stored in the state and used in the call. I also created the changeDealer const, which I can pass through the context, and which updates the state:
ApiContext.tsx
import React, { createContext, useEffect, useState } from 'react';
import axios from 'axios';
const contextObject = {} as any;
export const context = createContext(contextObject);
export const ApiContext = ({ children }: any) => {
const [dealerId, setDealerId] = useState(`1`);
useEffect(() => {
axios.get(`${process.env.GATSBY_API_ENDPOINT}/${dealerId}`).then((res) => setDealerId(res.data));
}, [dealerId]);
const changeDealer = (value: any) => {
setDealerId(value);
};
const { Provider } = context;
return <Provider value={{ data: dealerId, changeDealer: changeDealer }}>{children}</Provider>;
};
Then if, for example, I have a button that updates the dealer info, I import the context to the component and pass changeDealer through the it:
import { context } from 'contexts/ApiContext';
const { changeDealer } = useContext(context);
I can then attach it to a button like so:
<Link to="/" onClick={() => changeDealer(dealer.id)}>
Set Location
</Link>
This updates the state globally, changing the contact information across all the components that display it. I will be storing the data in a localStorage item, allowing the data to persist after a page refresh.