I have the following component which has a redirection route after an animation is finished, like so:
Menus.jsx
class Menus extends Component{
constructor (props) {
super(props);
this.state = {
select: 'espresso',
isLoading: false,
redirect: false
};
gotoCoffee = () => {
this.setState({isLoading:true})
setTimeout(()=>{
this.setState({isLoading:false,redirect:true})
},5000) //Replace this time with your animation time
}
renderCoffee = () => {
if (this.state.redirect) {
return (<Redirect to={`/coffee/${this.state.select}`} />)
}
}
render(){
return (
<div>
<h1 className="title is-1"><font color="#C86428">Menu</font></h1>
<hr/><br/>
<div>
{this.state.isLoading && <Brewing />}
{this.renderCoffee()}
<div onClick={this.gotoCoffee}
style={{textDecoration:'underline',cursor:'pointer'}}>
<strong><font color="#C86428">{this.state.coffees[0]}</font></strong></div>
</div>
</div>
);
}
}
export default withRouter(Menus);
The animation called onCLick:
Brewing.jsx
import React, { Component } from 'react';
import { withRouter } from 'react-router-dom';
import './css/mug.css'
class Brewing extends Component {
constructor (props) {
super(props);
};
render() {
return (
<div>
<div className="cup">
<div className="coffee"></div>
</div>
<div className="smoke"></div>
</div>
);
}
}
export default withRouter(Brewing);
And here redirected route component:
Coffee.jsx
class Coffees extends Component{
constructor (props) {
super(props);
this.state = {
select:'',
template:''
};
};
componentDidMount() {
if (this.props.isAuthenticated) {
this.getCoffee();
}
};
getCoffee(event) {
//const {userId} = this.props
const options = {
url: `${process.env.REACT_APP_WEB_SERVICE_URL}/coffee/espresso`,
method: 'get',
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${window.localStorage.authToken}`
}
};
return axios(options)
.then((res) => {
console.log(res.data.data)
this.setState({
template: res.data.data[0].content
})
})
.catch((error) => { console.log(error); });
};
render(){
var __html = this.state.template;
var template = { __html: __html };
return (
<div id="parent">
<h1 className="title is-1"><font color="#C86428">{this.state.select} playlist</font></h1>
<hr/><br/>
<div dangerouslySetInnerHTML={template}/>
</div>
);
}
}
export default withRouter(Coffees);
but <Redirect> in Menus.jsx is not working....url changes at browser but nothing happens; only if I refresh the browser /coffee is sucessfully mounted.
What I actually need to happen:
render Menu
click on a link
click renders an animation
when animation is done, after 5 seconds,
<Redirect> to /coffee
what am I missing?
When you say url changes at browser but nothing happens; only if I refresh the browser /coffee is sucessfully mounted.
This might be the issue with your Routes.
When you redirect to path /coffee/${this.state.select}, you should have Route to handle this path.
<Route path="/coffee/:select?" render={() => ( <Coffees isAuthenticated={true}/> )}/>
Note: Be aware of adding exact prop to Route. When you add exact prop it means your path should match exactly with all the provided params.
You need to call getCoffee function in also componentDidUpdate function.
componentDidMount() {
if (this.props.isAuthenticated) {
this.getCoffee();
}
};
componentDidUpdate() {
if (this.props.isAuthenticated) {
this.getCoffee();
}
};
Your Redirect should be inside the render().
render(){
if(this.state.redirect) {
return(<Redirect to={`/coffee/${this.state.select}`} />)
} else {
return (
<div>
...your component
</div> );
}
}
Note that this way you shouldn't need your renderCoffee() function.
I'm on mobile so i wasn't able to test if it works. Let me know if this works for you.
It seems your Menu component construtor has no closing bracket.
...
class Menus extends Component{
constructor (props) {
super(props);
this.state = {
select: 'espresso',
isLoading: false,
redirect: false
};
} // did you miss this?
gotoCoffee = () => {
...
Related
Context:
I want to trigger an event in a parents child component by an onClick on the parent element
Code:
Parent PlantContainer:
import React from "react";
import ClipLoader from "react-spinners/ClipLoader";
import Box from '#material-ui/core/Box';
import ShowMetric from '../showMetric';
export default class PlantContainer extends React.Component {
constructor(props) {
super(props);
this.state = {
isLoading: false,
};
}
render() {
return (
<Box>
<h2>{this.props.plantName}</h2>
<ShowMetric
setting={this.props.plantName + ".moisture"}
unit="%">Moisture:</ShowMetric>
<ShowMetric
setting={this.props.plantName + ".conductivity"}
unit="%">Fertility:</ShowMetric>
</Box>
);
}
}
Child ShowMetric:
import React from "react";
import ClipLoader from "react-spinners/ClipLoader";
import resolvePath from 'object-resolve-path';
export default class ShowMetric extends React.Component {
constructor(props) {
super(props);
this.getData = this.getData.bind(this);
this.state = {
isLoading: false,
reading: 0,
};
}
getData() {
this.setState({ isLoading: true });
fetch(URL_HERE, {
headers: {
"Content-Type": "application/json",
Accept: "application/json",
"Access-Control-Allow-Origin": "*",
"Access-Control-Allow-Headers": "*",
},
})
.then(function (response) {
return response.json();
})
.then((json) =>
this.setState({
reading: resolvePath(json, this.props.setting),
isLoading: false,
})
);
}
componentDidMount() {
this.getData();
}
render() {
if (this.state.isLoading) {
return <ClipLoader />;
}
return (
<div onClick={this.getData}>
{this.props.children + " "}
<nobr>{`${this.state.reading.toFixed(1)} ${this.props.unit}`}</nobr>
</div>
);
}
}
Main App.js:
import './App.css';
import React from 'react';
import Container from '#material-ui/core/Container';
import Box from '#material-ui/core/Box';
import PlantContainer from './components/plantContainer';
function App() {
return (
<div className="App">
<Container maxWidth="md">
<Box className="flexBox">
<PlantContainer plantName="Plant_1"/>
<PlantContainer plantName="Plant_2"/>
</Box>
</Container>
</div>
);
}
export default App;
Problem
The above code works as expected, as <ShowMetric/> shows the information and reloads when I click on it.
Now I want to reload all <ShowMetric/> Elements in PlantContainer (maybe trigger the getData() function for each of them) when I click the <H2> Element of PlantContainer.
I tried to find ways how to pass down events or informations to children, but since props can't change at runtime (?) and I don't think a reference would be the best way here, I am a bit at lost on how to implement this.
And as this is my very first react web App and endeavour into this framework please call out any fishy thing you can find in the code.
I think the more elegant way to do this would be to store all the data in the parent component and pass it down to the children through the props.
Here is a possible solution (I used function components as it should be privileged over the class components) :
PlantContainer
function fetchData() {
return fetch(URL_HERE, {
headers: {
"Content-Type": "application/json",
Accept: "application/json",
"Access-Control-Allow-Origin": "*",
"Access-Control-Allow-Headers": "*",
},
})
.then(response => response.json());
}
export default function PlantContainer(props) {
const [data, setData] = React.useState({
isLoading: false,
'moisture': 0,
'conductivity': 0
});
function loadData() {
setData({...data, isLoading: true});
fetchData().then(json => {
setData({
isLoading: false,
'moisture': resolvePath(json, `${props.plantName}.moisture`),
'conductivity': resolvePath(json, `${props.plantName}.conductivity`)
});
});
}
React.useEffect(loadData, []);
return (
<Box>
<h2 onClick={loadData}>{props.plantName}</h2>
{data.isLoading && <ClipLoader/>}
{!data.isLoading && (
<ShowMetric
reading={data['moisture']}
unit="%">Moisture:</ShowMetric>
<ShowMetric
reading={data['conductivity']}
unit="%">Fertility:</ShowMetric>
)}
</Box>
);
}
ShowMetric
export default function ShowMetric(props) {
return (
<div>
{props.children + " "}
<nobr>{`${props.reading.toFixed(1)} ${props.unit}`}</nobr>
</div>
);
}
As you can retrieve all the data by calling the service a single time, it seems to be useless to reload only one metric, so I only give to opportunity to reload both metrics by clicking on the h2 element.
The useImperativeHandle hook is perfect to allow child components and refs.
Fully working example with Typescript support too!:
//Child Component
//Create your ref types here
export type RefHandler = {
pressAlert: () => void;
inputRef: RefObject<HTMLInputElement>;
};
const Child = forwardRef<RefHandler, Props>((props, ref) => {
const submitRef = useRef<HTMLButtonElement>(null);
const inputRef = useRef<HTMLInputElement>(null);
//Initialise your refs here
useImperativeHandle(ref, () => ({
inputRef: inputRef,
pressAlert: () => submitRef?.current?.click()
}));
return (
<div>
<p>Child Component</p>
<input type="text" value="lorem ipsum" ref={inputRef} />
<br />
<button onClick={() => alert("Alert pressed")} ref={submitRef}>
Alert
</button>
</div>
);
});
//Parent
export default function Parent() {
const childRef = useRef<RefHandler>(null);
return (
<>
<p>Parent</p>
<button
onClick={() => {
alert(childRef?.current?.inputRef?.current?.value);
}}
>
Read child input
</button>
<button onClick={() => childRef?.current?.pressAlert()}>
Press child button
</button>
<hr />
<Child ref={childRef} />
</>
);
}
Edit: I have included the full code for better clarity
I am not sure if this is possible. I am trying to pass an onClick event via props but the click event does not work.
The parent component looks like this:
import React from 'react'
import { getProductsById } from '../api/get'
import Product from './Product'
import { instanceOf } from 'prop-types'
import { withCookies, Cookies } from 'react-cookie'
class Cart extends React.Component {
static propTypes = {
cookies: instanceOf(Cookies).isRequired
}
constructor(props) {
super(props)
const { cookies } = props;
this.state = {
prods: cookies.get('uircartprods') || '',
collapsed: true,
total: 0,
}
this.expand = this.expand.bind(this)
this.p = [];
}
componentDidMount() {
this.getCartProducts()
}
handleClick = (o) => {
this.p.push(o.id)
const { cookies } = this.props
cookies.set('uircartprods', this.p.toString(), { path: '/' , sameSite: true})
this.setState({prods: this.p })
console.log('click')
}
getCartProducts = async () => {
let products = []
if (this.state.prods !== '') {
const ts = this.state.prods.split(',')
for (var x = 0; x < ts.length; x++) {
var p = await getProductsById(ts[x])
var importedProducts = JSON.parse(p)
importedProducts.map(product => {
const prod = <Product key={product.id} product={product} handler={() => this.handleClick(product)} />
products.push(prod)
})
}
this.setState({products: products})
}
}
expand(event) {
this.setState({collapsed: !this.state.collapsed})
}
handleCheckout() {
console.log('checkout clicked')
}
render() {
return (
<div className={this.state.collapsed ? 'collapsed' : ''}>
<h6>Your cart</h6>
<p className={this.state.prods.length ? 'hidden' : ''}>Your cart is empty</p>
{this.state.products}
<h6>Total: {this.props.total}</h6>
<button onClick={this.handleCheckout} className={this.state.prods.length ? '' : 'hidden' }>Checkout</button>
<img src="./images/paypal.png" className="paypal" alt="Paypal" />
<a className="minify" onClick={this.expand} alt="My cart"></a>
<span className={this.state.prods.length ? 'pulse' : 'hidden'}>{this.state.prods.length}</span>
</div>
)
}
}
export default withCookies(Cart)
The Product component:
import React from 'react';
class Product extends React.Component {
constructor(props) {
super(props);
this.state = {
showDetails: false,
showModal: false,
cart: []
}
this.imgPath = './images/catalog/'
}
render() {
return (
<div className="Product">
<section>
<img src={this.imgPath + this.props.product.image} />
</section>
<section>
<div>
<h2>{this.props.product.title}</h2>
<h3>{this.props.product.artist}</h3>
<p>Product: {this.props.product.product_type}</p>
<h4>${this.props.product.price}</h4>
<button className="button"
id={this.props.product.id} onClick={this.props.handler}>Add to cart</button>
</div>
</section>
</div>
)
}
}
export default Product
If I log this.props.handler I get undefined. Everything works apart from the click handler, I was wondering if it might have something to with the async function. I am very new to React, there are still some concepts I'm not sure about, so any help is appreciated.
Okay, I see a few issues here.
First, there is no need to call this.handleClick = this.handleClick.bind(this) in the constructor, because you are using an arrow function. Arrow functions do not have a this context, and instead, accessing this inside your function will use the parent this found in the Class.
Secondly, it is wrong to store components in state. Instead, map your importedProducts inside the render function.
Thirdly, the issue with your handler is that this.props.handler doesn't actually call handleClick. You will notice in the definition handler={(product) => this.handleClick} it is returning the function to the caller, but not actually calling the function.
Try this instead.
class Product extends React.Component {
constructor(props) {
super(props);
}
render() {
return (
<div>
<button className="button" id={this.props.product.id} onClick={this.props.handler}>
Add to cart
</button>
</div>
);
}
}
export default Product;
import Product from './Product'
class Cart extends React.Component {
constructor(props) {
super(props);
}
handleClick = (o) => {
console.log('click');
};
render() {
return (
<div>
{importedProducts.map((product) => {
return <Product key={product.id} product={product} handler={() => this.handleClick(product)} />;
})}
</div>
);
}
}
export default Cart;
I have a Header.js component that takes a user's input and updates its state. I want to transfer(prop?) this item into the parent component App.js where it will be put in as a parameter and the data will be console logged relative to the user's input. I don't know how to transfer the state and implement it into the API's parameter.
class Header extends Component {
constructor() {
super();
this.state = {
query: '',
}
}
handleSubmit = (e) => {
e.preventDefault();
// form's input value
let userSearch = this.state.query;
}
handleChange = (e) => {
this.setState({
query: e.target.value
});
}
render() {
return (
<header>
<form onSubmit={this.handleSubmit}>
<input
onChange={this.handleChange}
type="text"
placeholder="Search"
/>
<label className="sr-only" htmlFor="search">Search News</label>
</form>
</header>
)
}
}
export default Header
import Header from './Components/Header'
import axios from 'axios';
class App extends Component {
constructor() {
super();
this.state = {
articles: [],
}
}
componentDidMount() {
axios({
// I want this.state.query in header.js to be {parameter}
url: 'http://newsapi.org/v2/everything?q={parameter}&sortBy=popularity&apiKey=where-the-key-goes',
method: 'GET',
responseType: 'JSON',
}).then((response => {
let articles = response.data.articles;
this.setState({
articles,
isLoading: false,
})
console.log(articles);
}))
}
render() {
return (
<>
<Header />
</>
)
}
}
export default App
You could create a callback function in the App component and pass to Header as a prop:
class App extends Component {
...
handleSearch = (value) => {
axios({
url: `http://newsapi.org/v2/everything?q=${value}&sortBy=popularity&apiKey=where-the-key-goes`,
method: "GET",
responseType: "JSON",
}).then((response) => { ... });
};
render() {
return (
<>
<Header handleSearch={this.handleSearch} />
</>
);
}
}
Then use it in the Header's handleSubmit function:
handleSubmit = (e) => {
e.preventDefault();
// form's input value
let userSearch = this.state.query;
this.props.handleSearch(userSearch);
};
class Header extends Component<Props> { // add Props
...
handleSubmit = (e) => {
e.preventDefault();
// form's input value
let userSearch = this.state.query;
this.props.onValueSet(userSearch); // callback
}
...
}
class App extends Component {
...
// add callback
_setValueHandle = (value) => {
console.log(value);
this.setState({parameter: value}); // do something u want
};
render() {
return (
<>
{/* set callback */}
<Header onValueSet={this._setValueHandle} />
</>
)}
...
}
how about this?
You have to add props from App.js. Also you don't need to call the api on componentDidMount because you want have the query yet. Try this:
class Header extends Component {
static defaultProps = {
onUpdate: () => {},
onSubmission: () => {}
}
constructor() {
super();
this.state = {
query: '',
}
}
handleSubmit = (e) => {
e.preventDefault();
// form's input value
let userSearch = this.state.query;
this.props.onSubmission(this.state.query); //Send submission to parent
}
handleChange = (e) => {
this.setState({
query: e.target.value
}, () => {
this.props.onUpdate(this.state.query); //Send change to parent
});
}
render() {
return (
<header>
<form onSubmit={this.handleSubmit.bind(this)}>
<input
onChange={this.handleChange.bind(this)}
type="text"
placeholder="Search"
/>
<label className="sr-only" htmlFor="search">Search News</label>
</form>
</header>
)
}
}
export default Header
import Header from './Components/Header'
import axios from 'axios';
class App extends Component {
constructor() {
super();
this.state = {
articles: [],
query: ""
}
}
componentDidMount() {
}
request(query){
axios({
// I want this.state.query in header.js to be {parameter}
//NOTE: Query could also be this.state.query since we're updating it on change
url: 'http://newsapi.org/v2/everything?q={parameter}&sortBy=popularity&apiKey=where-the-key-goes',
method: 'GET',
responseType: 'JSON',
}).then((response => {
let articles = response.data.articles;
this.setState({
articles,
isLoading: false,
})
console.log(articles);
}))
}
render() {
return (
<>
<Header onUpdate={query => this.setState({query: query})} onSubmission={this.request.bind(this)} />
</>
)
}
}
export default App
in project i have 3 page
1)i need when i click in button 'Next' in page 1 redirect to page 2 and show toast Component in page2
2)when click'Next' in page 2 redirect to page 3
3)and when click button 'back' in page 3 redirect to page 2 but not show toast in page2,
How do slove this chalange, redirect work correctly i have problem in show toast or not show in page 2
---page1---
class page1 extends Component {
handleSubmit = (e) => {
this.props.history.push('/page2');
};
render() {
return (
<Fragment>
<Button
title={"Next"}
onClick={this.handleSubmit}
/>
</Fragment>
);
}
}
---page2 - i write state for toast in componentDidMount f---
class page2 extends Component {
constructor(props) {
super(props);
this.state = {
showToast: false,
messageToast: '',
levelToast: 'success',
}
}
componentDidMount() {
this.setState({
showToast: true,
messageToast: 'Ok',
levelToast: 'success',
}, () => {
setTimeout(() => this.setState({showToast: false}), 3000)
})
}
handleSubmit = () => {
this.props.history.push('/page3');
};
render() {
return (
<Fragment>
<Button
title={"Next"}
onClick={this.handleSubmit}
/>
</Fragment>
);
}
}
--page3---
class page3 extends Component {
handleBack = (e) => {
e.preventDefault();
this.props.history.push('/page2');
};
render() {
return (
<Fragment>
<Button
type={"close"}
title={"back"}
id={'b'}
onClick={this.handleBack}
/>
</Fragment>
);
}
}
Assuming you're using React Router, the easiest way would be to use the second argument that goes into the the history.push function. You could do something like:
Page1.js
class Page1 extends Component {
handleSubmit = (e) => {
this.props.history.push('/page2', {showToast: true});
// the second argument sets a state value that can be accessed in the `Page2` component
};
render() {
return (
<Fragment>
<Button
title={"Next"}
onClick={this.handleSubmit}
/>
</Fragment>
);
}
}
Page2.js
class Page2 extends Component {
constructor(props) {
super(props);
this.state = {
showToast: false,
messageToast: '',
levelToast: 'success',
}
}
componentDidMount() {
// this is the `state` argument in the `handleSubmit` function on Page1
const shouldShowToast = this.props.location.state && this.props.location.state.showToast
this.setState({
showToast: shouldShow,
messageToast: 'Ok',
levelToast: 'success',
}, () => {
setTimeout(() => this.setState({showToast: false}), 3000)
})
}
...
...
}
'Page3.js`
class Page3 extends Component {
handleBack = (e) => {
e.preventDefault();
this.props.history.push('/page2', {showToast: false}); // set the location state.showToast to `false` this time.
};
...
...
}
Hope that helped!
Whenever i type something is search bar update the state, it skips the first letter. For example, if i write "asdf" it only shows "sdf".
I tried console.log before this line of code
this.props.newQuery(this.state.newSearchQuery);
and everything was working fine.
Please check the below code of App.js and SearchBar.js
Thanks
App.js
import React from 'react';
import SearchBar from './components/SearchBar';
class App extends React.Component {
constructor(){
super();
this.state = {
searchQuery: '',
fetchedData: []
};
}
newQuery(query){
this.setState({
searchQuery: query
});
}
onSearch(){
const userInput = this.state.searchQuery;
if(userInput !== '' && userInput !== ' '){
const API_KEY = `https://pokeapi.co/api/v2/pokemon/${userInput}`;
fetch(API_KEY, {
method: 'GET',
headers: {
Accept: 'application/json'
}
})
.then(result => result.json())
.then(data => this.setState({ fetchedData: data.results }));
console.log('res', this.state.fetchedData);
}
}
render(){
return(
<div className="App">
<h2>Search Pokemos by Types</h2>
<hr />
<SearchBar onSearch={this.onSearch.bind(this)} newQuery={this.newQuery.bind(this)} />
</div>
);
}
}
export default App;
SearchBar.js
import React from 'react';
class SearchBar extends React.Component {
constructor(props){
super(props);
this.state = {
newSearchQuery: '' //this blank value get executed first when i console.log
}
}
searchInput(event){
this.setState({
newSearchQuery: event.target.value
});
this.props.newQuery(this.state.newSearchQuery);
console.log(this.state.newSearchQuery); // if i log "asdf", state on top "newSearchQuery" skips the first letter, a and shows "sdf" only.
}
render(){
return(
<div className="input-group">
<input onChange={this.searchInput.bind(this)} className="form-control" placeholder="[eg. Ditto, Cheri, etc]" />
<button onClick={this.props.onSearch} className="btn btn-success">Search</button>
</div>
);
}
}
export default SearchBar;
The console.log doesnot log as expected because the setState() method is not always executed as its called. According to Docs
State Updates May Be Asynchronous:Because this.props and this.state may be updated asynchronously, you should not rely on their values for calculating the next state.
So when you are logging this console.log(this.state.newSearchQuery); after the setState the state is not actually changed so thats why it logs unexpected
I'm a bit confused as to the redundant state between the two components. What I think I would do (if I wasn't using something like mobx) is keep the state on the parent component and pass the handleChange and handleSearch functions to the <Search /> component. It would look something like...
I threw together a codesandbox for you: https://codesandbox.io/s/n1ryz4rzwl
APP Component
import React, { Component } from 'react';
import SearchBar from './components/SearchBar'
class App extends Component {
constructor() {
super()
this.state = {
searchQuery: '',
fetchedData: []
}
}
handleChangeQuery = event => this.setState({searchQuery: event.target.value})
handleSearch = () => {
const {searchQuery} = this.state,
API_KEY = `https://pokeapi.co/api/v2/pokemon/${searchQuery}`;
fetch(API_KEY, {
method: 'GET',
headers: {
Accept: 'application/json'
}
})
.then(result => result.json())
.then(data => this.setState({ fetchedData: data.results }));
}
render() {
const {searchQuery} = this.state
return (
<div className="App">
<h2>Search Pokemos by Types</h2>
<hr />
<SearchBar
value={searchQuery}
handleChangeQuery={this.handleChangeQuery}
handleSearch={this.handleSearch}
/>
// Show fetchedData results here
</div>
)
}
}
export default App
SearchBar Component - This COULD be a stateless functional component
import React from 'react'
const SearchBar = ({value, handleChangeQuery, handleSearch}) => {
return (
<div className="input-group">
<input
onChange={handleChangeQuery}
value={value}
className="form-control"
placeholder="[eg. Ditto, Cheri, etc]"
/>
<button onClick={handleSearch} className="btn btn-success">Search</button>
</div>
)
}
export default SearchBar
The reasoning behind the strange missing character has been described by the other comments - as the this.setState() may be async. However, this.setState() does have a callback function that can be used to confirm the change if you want to test with that. It looks something like:
this.setState({key: value}, () => {
// State has been set
console.log(this.state.key)
})