React prop not being passed to component - reactjs

The code below is a react component called Start. Its function is to take in a players name via a form with onSubmit. It stores the players name in a hook called "player". Then it passes the player name prop to another component called GameBoard. Once the submit button is pressed the browser navigates to the GameBoard component via react-router-dom. The GameBoard Component is then supposed to display the players name that passed to it in the Start component.
The issue I'm having is that the player name state is not being passed into the GameBoard component. When onSubmit is initiated the page changes to the GameBoard but the player name doesn't get passed. Any ideas?
import React, { useState, useEffect } from "react";
import { BrowserRouter as Router, Switch, Route } from "react-router-dom";
//styles
import { Wrapper, Content } from "../styles/start.styles";
//component
import GameBoard from "../components/gameBoard.component.jsx";
const Start = (props) => {
const [player, setPlayer] = useState("");
let handleChange = (event) => {
event.preventDefault();
setPlayer(event.target.value);
};
let handleSubmit = () => {
if (player === "") {
alert("Please enter a players name");
} else {
window.history.replaceState(null, "GameBoard", "/gameboard");
}
};
useEffect(() => {
console.log(player);
});
return (
<Router>
<Switch>
<Route exact path="/">
<Wrapper>
<Content>
<h1>Trivia Words</h1>
<h2>Start Menu</h2>
<form onSubmit={handleSubmit}>
<label>Enter Player Name:</label>
<input type="text" onChange={handleChange}></input>
<input type="submit" value="Start"></input>
</form>
</Content>
</Wrapper>
</Route>
<Route exact path="/gameboard">
<GameBoard playerName={player} />
</Route>
</Switch>
</Router>
);
};
export default Start;
GameBoard Component below
import React, { useState } from "react";
//styles
import { Wrapper, Content } from "../styles/gameBoard.styles";
const GameBoard = (props) => {
const [playerName, setPlayerName] = useState(props.playerName);
const [letters, setLetters] = useState([]);
const [triviaQA, setTriviaQA] = useState([]);
const [gameOver, setGameOver] = useState(false);
return (
<Wrapper>
<Content>Player Name: {playerName}</Content>
</Wrapper>
);
};
export default GameBoard;

creating a derived state from props is in general bad practice. you should consume your props directly, and if you need to update its state at Child Component you should pass a setState prop as well:
import React, { useState } from "react";
//styles
import { Wrapper, Content } from "../styles/gameBoard.styles";
const GameBoard = (props) => {
const [letters, setLetters] = useState([]);
const [triviaQA, setTriviaQA] = useState([]);
const [gameOver, setGameOver] = useState(false);
return (
<Wrapper>
<Content>Player Name: {props.playerName}</Content>
</Wrapper>
);
};
export default GameBoard;
also, you seem not using the proper navigation from react-router-dom at your handleSubmit.
you could create a Form Player component and import useHistory and push to the desired route to be able to use useHistory or wrap your component with BrowserRouter:
import React, { useState, useEffect } from "react";
import { BrowserRouter as Router, Switch, Route, useHistory } from "react-router-dom";
//styles
import { Wrapper, Content } from "../styles/start.styles";
//component
import GameBoard from "../components/gameBoard.component.jsx";
const Start = (props) => {
const [player, setPlayer] = useState("");
let handleChange = (event) => {
event.preventDefault();
setPlayer(event.target.value);
};
useEffect(() => {
console.log(player);
});
return (
<Router>
<Switch>
<Route exact path="/">
<Wrapper>
<Content>
<h1>Trivia Words</h1>
<h2>Start Menu</h2>
<PlayerNameForm player={player} handleChange={handleChange} />
</Content>
</Wrapper>
</Route>
<Route exact path="/gameboard">
<GameBoard playerName={player} />
</Route>
</Switch>
</Router>
);
};
export default Start;
const PlayerNameForm = ({player, handleChange}) => {
const history = useHistory();
let handleSubmit = () => {
if (player === "") {
alert("Please enter a players name");
} else {
history("/gameboard");
}
};
return (
<form onSubmit={handleSubmit}>
<label>Enter Player Name:</label>
<input type="text" onChange={handleChange}></input>
<input type="submit" value="Start"></input>
</form>
)
}

Related

react send data from child component to parent

Main.js (Parent)
import axios from 'axios'
import React, { Component, useEffect, useState } from 'react'
import { BrowserRouter as Router, Switch, Route} from "react-router-dom";
import Books from './Books'
import Navbar from './Navbar'
import AddBook from './AddBook';
import BookDetail from './BookDetail';
const Main = () => {
let [books, setBooks] = useState([])
useEffect(() => {
axios.get('http://127.0.0.1:8000/api/get-books')
.then(res => {
setBooks(books = res.data)
})
})
return (
<Router>
<div>
<Navbar title='LunaBooks'/>
<div className='container'>
<Switch>
<Route exact path='/' >
<Books books={books} />
</Route>
<Route path='/add-book' >
<AddBook />
</Route>
<Route path='/details/' >
<BookDetail bookName={} /> // put the bookName from Books.js here <----
</Route>
</Switch>
</div>
</div>
</Router>
)
}
export default Main
Books.js (Child)
import React, { Component, useState } from 'react'
import BookDetail from './BookDetail'
const Books = ({books}) => {
let [bookName, setBookName] = useState('') // send the bookname to Main.js
const SendBookDetails = e => {
let bookName = e.target.value
setBookName(bookName = bookName)
}
return (
<div>
<div className='books'>
{books.map(book => (
<div className='book'>
<h2>{book.name}</h2>
<p>Author: <a href='#'>{book.author}</a></p>
<button className="btn" value={book.name} onClick={SendBookDetails}>View Details</button>
</div>
))}
</div>
</div>
)
}
export default Books
To put it simply, I want to send the bookName that is located in Books.js and put in the Main.js so that I can pass it in BookDetail.js, I know how to do in the reverse way but this is just confusing me for some reason... I'm pretty new to react so please bear with me!
Make a selectedBook state in the parent.
const [selectedBook, setSelectedBook] = useState()
Pass a callback function as a prop "onBookSelect" to the child.
<Books books={books} onBookSelect={(book) => { setSelectedBook(book) } />
Then in the child call the callback function with new value. in your SendBookDetails function.
const Books = ({books, onBookSelect}) => {
//...
const SendBookDetails = e => {
let bookName = e.target.value
setBookName(bookName)
onBookSelect(bookName)
}
Then pass that state to the props of your book details component.
<BookDetail bookName={selectedBook} />

React - Using state in component by useContext

Hello I got stuck during creating app using hooks
I do not why but my Component does not download a state from my Context Component or maybe my initial state does not update correctly. Does somebody have any idea what's going on?
Context Component:
import React, { createContext, useState } from 'react';
export const WeatherDataContext = createContext();
const WeatherDataContextProvider = (props) => {
const [weather, setWeather] = useState(
{
city: null,
temp: null
}
)
const addWeather = (city, temp) => {
setWeather({
city,
temp
})
}
return (
<WeatherDataContext.Provider value={{weather, addWeather}}>
{props.children}
</WeatherDataContext.Provider>
)
}
export default WeatherDataContextProvider
Form - axios - Component:
import React, {useContext, useState} from 'react';
import { WeatherDataContext } from '../context/WeatherDataContext';
import axios from 'axios'
import {Link} from 'react-router-dom'
const WeatherForm = () => {
const {addWeather} = useContext(WeatherDataContext);
const [value, setValue] = useState('')
const handleChange = (e) => {
e.preventDefault();
axios.get(`https://api.openweathermap.org/data/2.5/weather?q=${value}&appid=${KEY}&units=metric`)
.then(res => {
addWeather(res.data.name, res.data.main.temp)
})
}
return (
<div class='weather-form'>
<form onSubmit={handleChange}>
<input placeholder='City' onChange={(e) => setValue(e.target.value)} value={value} required/>
<Link to='/weather'><button>Search</button></Link>
</form>
</div>
)
}
export default WeatherForm
And final component where I want to use my update state
import React, {useContext, useState} from 'react';
import { WeatherDataContext } from '../context/WeatherDataContext';
const WeatherFront = () => {
const {weather} = useContext(WeatherDataContext)
console.log(weather)
return (
<div class='weather-front'>
<h1>City: {weather.city}, Temperatura: {weather.temp}</h1>
</div>
)
}
export default WeatherFront
Your button is not submitting the form - it navigates away from the page instead.
So handleChange is not being called.
You can call it from buttons onClick instead of forms onSubmit. Be sure to omit e.preventDefault() then, so that parent Link can still navigate.
const WeatherForm = () => {
const { addWeather } = useContext(WeatherDataContext)
const [value, setValue] = useState('')
const handleChange = (e) => {
axios.get(`https://api.openweathermap.org/data/2.5/weather?q=${value}&appid=${KEY}&units=metric`)
.then(res => {
addWeather(res.data.name, res.data.main.temp)
})
}
return (
<div class="weather-form">
<form >
<input
placeholder="City"
onChange={(e) => setValue(e.target.value)}
value={value}
required
/>
<Link to="/weather">
<button onClick={handleChange}>Search</button>
</Link>
</form>
</div>
)
}
Be sure to wrap both pages inside the same context:
<WeatherDataContextProvider>
<Router>
<Switch>
<Route path="/weather">
<WeatherFront></WeatherFront>
</Route>
<Route path="/">
<WeatherForm></WeatherForm>
</Route>
</Switch>
</Router>
</WeatherDataContextProvider>

Not able to access detail information from Api using React Router not rendering on the page

I am building a small React Routing application to get a better idea as to how it work. My App.js looks like this with the basic routing:
import React from 'react';
import './App.css';
import Nav from './Nav';
import About from './About';
import Shop from './Shop';
import CountryDetail from './CountryDetail'
import { BrowserRouter as Router, Switch, Route } from 'react-router-dom';
function App() {
return (
<Router>
<div className="App">
<Nav />
<Switch>
<Route path="/" exact component={Home} />
<Route path="/about" component={About} />
<Route path="/shop" exact component={Shop} />
<Route path="/shop/:name" component={CountryDetail} />
</Switch>
</div>
</Router>
);
}
const Home = () => (
<div>
<h1>Home Page</h1>
</div>
);
Now the Shop component a list of countries from the api which is in the code below:
import React from 'react';
import './App.css';
import { useEffect } from 'react';
import { useState } from 'react';
import {Link} from 'react-router-dom';
function Shop() {
useEffect(() => {
fetchItems();
},[])
const [countries, setCountries] = useState([])
const fetchItems = async () => {
const data = await fetch('https://restcountries.eu/rest/v2/all');
const countries = await data.json();
console.log(countries);
setCountries(countries);
}
return (
<div>
{countries.map(country => (
<div>
<Link to={`shop/${country.name}`}>
<h1 key={country.alpha2Code}>
{country.name}
</h1>
</Link>
<p>Popluation {country.population}</p>
<p> Region {country.region}</p>
<p>Capital {country.capital}</p>
</div>
)
)}
</div>
);
}
export default Shop;
Now what I want to do is render more information about the country when I click on it. So I have created another component called CountryDetail:
import React from 'react';
import './App.css';
import { useEffect } from 'react';
import { useState } from 'react';
function CountryDetail( { match } ) {
useEffect(() => {
fetchItem();
console.log(match)
},[])
const [country, setCountry] = useState([])
const fetchItem = async ()=> {
const fetchCountry = await fetch(`https://restcountries.eu/rest/v2/name/${match.params.name}`);
const country = await fetchCountry.json();
setCountry(country);
console.log(country);
}
return (
<div>
<h1>Name {country.name}</h1>
<p>Native Name{country.nativeName}</p>
<p>Region {country.region}</p>
<p>Languages {country.languages}</p>
<h1>This Country</h1>
</div>
);
}
export default CountryDetail;
The problem I am having is that it is not rendering anything on the CountryDetail page. I am sure I have hit the api correctly but not sure if I am getting the data correctly. Any help would be appreciated.
Issue: The returned JSON is an array but your JSX assumes it is an object.
Solution: You should extract the 0th element from the JSON array. Surround in a try/catch in case of error, and correctly render the response.
Note: the languages is also an array, so that needs to be mapped
function CountryDetail({ match }) {
useEffect(() => {
fetchItem();
console.log(match);
}, []);
const [country, setCountry] = useState(null);
const fetchItem = async () => {
try {
const fetchCountry = await fetch(
`https://restcountries.eu/rest/v2/name/${match.params.name}`
);
const country = await fetchCountry.json();
setCountry(country[0]);
console.log(country[0]);
} catch {
// leave state alone or set some error state, etc...
}
};
return (
country && (
<div>
<h1>Name {country.name}</h1>
<p>Native Name{country.nativeName}</p>
<p>Region {country.region}</p>
<p>Languages {country.languages.map(({ name }) => name).join(", ")}</p>
<h1>This Country</h1>
</div>
)
);
}
As you said it yourself, the response is an array (with a single country object in it), but you are using it as if it would be an object.
So, instead of:
const country = await fetchCountry.json();
setCountry(country);
It should be:
const countries = await fetchCountry.json();
setCountry(countries[0]);

Switch to results Component `onSubmit` a search form in react hooks using react router

Hi I have a scenario where I put a search bar on the top nav so a user can search from anywhere in the app. How to do I switch to the results component once the user submits the search form? Here's my search component that populates the global state with search results but I can't manage to switch the view to the results component.
import React, { useState, useEffect, useContext } from 'react';
import axios from 'axios';
import { StateContext } from '../../StateContext';
import './SearchBar.scss';
import sprite from '../../assets/icons/sprite.svg';
function SearchBar() {
const [state, setState] = useContext(StateContext);
const [userInput, setUserInput] = useState('');
const [bookName, setBookName] = useState('');
useEffect(() => {
axios
.get(`https://www.googleapis.com/books/v1/volumes?q=${bookName}`)
.then((res) => {
let book_list = res.data.items;
setState({
book_list: book_list,
heading: 'Search Results'
});
})
.catch((err) => console.log(err));
}, [bookName]);
const findBook = (e) => {
e.preventDefault();
setBookName(userInput);
};
const onChange = (e) => {
setUserInput(e.target.value);
};
return (
<form className='searchbar' onSubmit={findBook}>
<input
type='search'
className='searchbar__input'
placeholder='Search for a book'
value={userInput}
onChange={onChange}
/>
<button className='searchbar__button'>
<svg className='searchbar__icon'>
<use xlinkHref={`${sprite}#icon-search`} />
</svg>
</button>
</form>
);
}
export default SearchBar;
Here's how I'm handling routing:
import React from 'react';
import Nav from './components/Nav/Nav';
import { BrowserRouter as Router, Route, Switch } from 'react-router-dom';
import Books from './containers/Books';
import Book from './containers/Book';
import { ContextController } from './StateContext';
function App() {
return (
<ContextController>
<Router>
<div className='app'>
<Nav />
<main>
<Switch>
<Route exact path='/' component={Books} />
<Route exact path='/book/:id' component={Book} />
</Switch>
</main>
</div>
</Router>
</ContextController>
);
}
export default App;
If you have a dedicated route for search results, try this in your ContextController
import { useHistory } from 'react-router-dom';
// later
const history = useHistory();
React.useEffect(() => {
if (state?.book_list?.length > 0) {
history.push('/search-results');
}
}, [state]);
Also, it is important to note that the Router should be on top of your Data Context;
Because if you want to access the history from the a tree, it needs to be wrapped in a Router, or else it will return undefined as a value for history
Here is a working codesandbox

React createContext/useContext does not survive between pages

I am trying to create a shared global state for all components that an app needs, and instead of relying on props drilling or redux, I am trying to achieve that with the React Context.
Why does my user context not survive when I switch between routes? The application bellow illustrates the issue.
Do I need to use any other hook in conjunction with useContext?
//index.js
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import { AuthenticationProvider } from "./AuthenticationProvider";
const Index = () => {
return (
<AuthenticationProvider>
<App />
</AuthenticationProvider>
);
}
ReactDOM.render(<Index />, document.getElementById('root'));
//App.js
import React, { useState, useContext } from 'react';
import { BrowserRouter as Router, Route, Switch } from "react-router-dom";
import './App.css';
import { AuthenticationContext } from './AuthenticationProvider';
function AddUser() {
const [formUser, setFormUser] = useState("");
const [user, setUser] = useContext(AuthenticationContext);
const handleSubmit = async (event) => {
event.preventDefault();
setUser(formUser);
}
return (
<React.Fragment>
Form user: {formUser}.
<form id="form1" onSubmit={handleSubmit}>
<input type="text" id="user" onChange={event => setFormUser(event.target.value)} />
<input type="submit" value="Save" />
</form>
<br/>
Current user: {user}
<br/>
Back to home
</React.Fragment>
);
}
function Home() {
const [user, setUser] = useContext(AuthenticationContext);
return (
<React.Fragment>
<div className="App">
Hello {user}.
<br/>
Add user
</div>
</React.Fragment>
);
}
function App() {
return (
<Router>
<Switch>
<Route exact path="/" component={Home} />
<Route exact path="/add" component={AddUser} />
</Switch>
</Router>
);
}
export default App;
//AuthenticationProvider.js
import React, { useState, createContext } from "react";
const DEFAULT_STATE = "";
export const AuthenticationContext = createContext(DEFAULT_STATE);
export const AuthenticationProvider = ({ children }) => {
const [user, setUser] = useState(DEFAULT_STATE);
return (
<AuthenticationContext.Provider value={[user, setUser]} >
{children}
</AuthenticationContext.Provider>
);
}
The problem is that you used a regular <a> link to navigate through the app and every time you go from Home to addUser the app refreshes. To navigate through the app without refreshing the page use the Link component from react-router-dom
in Home and AddUser change the a links to the Link component
import { Link } from "react-router-dom";
function Home() {
const { user, setUser } = useContext(AuthenticationContext);
return (
<React.Fragment>
<div className="App">
Hello {user}.
<br />
<Link to="/add">Add user</Link> <-- Changed a to Link
</div>
</React.Fragment>
);
}
function AddUser() {
const [formUser, setFormUser] = useState("");
const [user, setUser] = useContext(AuthenticationContext);
const handleSubmit = async (event) => {
event.preventDefault();
setUser(formUser);
}
return (
<React.Fragment>
Form user: {formUser}.
<form id="form1" onSubmit={handleSubmit}>
<input type="text" id="user" onChange={event => setFormUser(event.target.value)} />
<input type="submit" value="Save" />
</form>
<br />
Current user: {user}
<br />
<Link to="/">Back to home</Link> <-- Changed a to Link
</React.Fragment>
);
}

Resources