How to change state when in other page click? - javascript

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!

Related

How to prevent click event for one component from another?

Please suggest how to Enable/Disable Toggle button from another component in ReactJS?
The idea is to Enable/Disable Toggle button from Statistics' fetchStatistics() function. When the logic of fetchStatistics() checks the response it should block Toggle button click or Enable click event for it.
ProviderContainer - root container
var ProviderContainer = React.createClass({
getInitialState: function() {
},
render() {
return (
<div className="app">
<Provider />
<Provider />
</div>
);
}
});
Provider component
var Provider = React.createClass({
getInitialState: function() {
},
render: function() {
return (
<li>
<Statistics />
<Toggle />
</li>
);
}
});
Statistics component
var Statistics = React.createClass({
fetchStatistics: function() {
let url = "https://localhost/statistics";
fetch(url)
.then(data => {
if (data) {
// Disable click for <Toggle> button
} else {
// Enable click for <Toggle> button
}
})
.catch((error) => {
console.log('error', error);
});
},
render: function(){
return(
<div>
...
</div>
);
}
});
Toggle component
class Toggle extends React.Component {
constructor(props) {
super(props);
this.handleClick = this.handleClick.bind(this);
}
handleClick(e) {
}
componentWillReceiveProps(nextProps) {
}
render() {
return (
<div className="toogle active" onClick={this.handleClick} >
<div className="toggle__manage"></div>
</div>
);
}
};
See the code below. I have modified the Provider, Statistics, and Toggle components.
In the Provider component you'll need to manage a allowToggle state which is passed down to the Toggle component. The state can be changed via the setAllowToggle function which is passed down to the Statistics component.
var Provider = React.createClass({
getInitialState: function() {
return {allowToggle: false};
},
setAllowToggle: function(allowToggle) {
this.setState({ allowToggle });
},
render: function() {
return (
<li>
<Statistics setAllowToggle={this.setAllowToggle}/>
<Toggle allowToggle={allowToggle}/>
</li>
);
}
});
In the Statistics component the setAllowToggle and is called with true or false as per the condition.
var Statistics = React.createClass({
fetchStatistics: function() {
let url = "https://localhost/statistics";
fetch(url)
.then(data => {
if (data) {
// Disable click for <Toggle> button
this.props.allowToggle(false);
} else {
// Enable click for <Toggle> button
this.props.allowToggle(true);
}
})
.catch((error) => {
console.log('error', error);
});
},
render: function(){
return(
<div>
...
</div>
);
}
});
In the Toggle component the allowToggle prop is used to decide whether to allow toggle or not. Also, the active css class and the onClick function are conditionally provided.
class Toggle extends React.Component {
constructor(props) {
super(props);
this.handleClick = this.handleClick.bind(this);
}
handleClick(e) {
}
componentWillReceiveProps(nextProps) {
}
render() {
return (
<div
className={`toggle ${this.props.allowToggle ? 'active' : ''`}
{...(this.props.allowToggle && {
onClick: this.handleClick
})}
>
<div className="toggle__manage"></div>
</div>
);
}
};

React - Show loader on Click that already has function assigned to it

I have already a a click event within a ternary operator that does a GET request from my API. When the button is clicked, the button disappears and the data text replaces the button (button disappears). But there is a small gap of time between the get request and the text reveal. I want to put a loading message of some kind at that moment of time so the user knows something is happening. But can't seem to figure it out. Here is my code:
import React, {Component} from "react";
import axios from "axios";
export default class FoodData extends React.Component {
constructor(props) {
super(props);
this.state = {
meal: '',
clicked: false,
isLoaded: false,
}
this.handleClick = this.handleClick.bind(this);
}
handleClick() {
this.setState({
clicked: true,
});
}
fetchData() {
axios.get('api/menu/food')
.then(res => {
const meal= `${res.data.starters},${ res.data.price}`;
this.setState({
meal: meal,
isLoaded: true
})
console.log(meal)
})
};
combinedFunction() {
this.fetchData();
this.handleClick();
}
render(){
const {isLoaded, meal} = this.state;
return (
<div >
Dish: {
this.state.clicked ? (
this.state.menu
) : (
<button onClick={() => { this.combinedFunction() }}>Find Dish</button>
)}
</div>
);
}
}
Appreciate the help.
What you can do is add a "isLoading" state and put the values before and after your API call like so:
fetchData() {
this.setState({isLoading: true});
axios.get('api/menu/food')
.then(res => {
const meal= `${res.data.starters},${ res.data.price}`;
this.setState({
meal: meal,
isLoaded: true
isLoading: false,
})
console.log(meal)
})
};
And use that on your render to show the "loading icon"
render(){
const {isLoaded, meal, isLoading } = this.state;
return (
<div >
{isLoading ? <div>loading</div> :
Dish: {
this.state.clicked ? (
this.state.menu
) : (
<button onClick={() => { this.combinedFunction() }}>Find Dish</button>
)}
}
</div>
);
}
}
This is a working demo which shows loading when api call starts and disables button to prevent multiple api calls. I added a 2sec time out to show the demo. Check the stackblitz sample
This is the updated code, here I used a fake api (https://jsonplaceholder.typicode.com/users) to show the demo
import React, {Component} from "react";
import axios from "axios";
export default class FoodData extends React.Component {
constructor(props) {
super(props);
this.state = {
meal: '',
clicked: false,
isLoaded: false,
}
this.handleClick = this.handleClick.bind(this);
this.combinedFunction = this.combinedFunction.bind(this)
}
handleClick() {
this.setState({
clicked: true,
});
}
fetchData() {
axios.get('https://jsonplaceholder.typicode.com/users')
.then(res => {
this.setState({
meal: res.data,
isLoaded: false
})
})
};
combinedFunction =()=> {
this.setState({isLoaded: true})
setTimeout(()=>{
this.fetchData();
},2000)
this.handleClick();
}
render(){
const {isLoaded, meal} = this.state;
return (
<>
<div >
Users:
<button onClick={this.combinedFunction } disabled={isLoaded ? true : false}>{isLoaded ? 'Loading...':'Find User'}</button>
</div>
<div>
{meal && meal.map(item =>(
<div key={item.id}>
<p>{item.id} - {item.name}</p>
</div>
))}
</div>
</>
);
}
}

Include Modal functionality in React Higher-Order Component

I created the below HOC which I can use to wrap a React component to add 2 inactivity timers: the first to show the user a warning; the other to log them out. I got the idea from here and it seems to work pretty well. That is, I can add withTimer functionality to a component by wrapping it like this:
export default withTimer(DragDropContext(HTML5Backend)(App));
The problem is the warning alert box halts the event loop (as alert boxes apparently always do), so the logout function is never reached.
I believe a modal (e.g., from react-bootstrap) would solve this, as it presumably would not halt the event loop, thus the logout would occur as intended if the user is still idle after the warning alert.
How would I change the below HOC to use a modal for the warning instead of an alert box? Is this possible? That is, can a HOC that's used to wrap another component include a component itself (i.e., the modal) so as to keep it decoupled from the wrapped component itself?
import React from 'react';
import { Modal } from 'react-bootstrap';
const withTimer = (WrappedComponent) => {
class WithTimer extends React.Component {
constructor(props) {
super(props);
this.state = {
warningTime: 5000,
signoutTime: 10000
};
this.events = [
'load',
'mousemove',
'mousedown',
'click',
'scroll',
'keypress'
];
for (var i in this.events) {
window.addEventListener(this.events[i], this.resetTimeout);
}
this.setTimeout();
}
clearTimeoutFunc = () => {
if (this.warnTimeout) clearTimeout(this.warnTimeout);
if (this.logoutTimeout) clearTimeout(this.logoutTimeout);
};
setTimeout = () => {
this.warnTimeout = setTimeout(this.warn, this.state.warningTime);
this.logoutTimeout = setTimeout(this.logout, this.state.signoutTime);
};
resetTimeout = () => {
this.clearTimeoutFunc();
this.setTimeout();
};
warn = () => {
window.alert('You will be logged out soon. Click to stay logged in.');
};
logout = () => {
window.alert('You are being logged out!');
// log the user out here
};
render() {
console.log('HOC');
return <WrappedComponent {...this.props.children} />;
}
}
return WithTimer;
};
export default withTimer;
If you wanted to use a Modal, you could do something like this:
Live Demo
withTimer.js
import React from 'react';
import MyModal from './MyModal';
const withTimer = (WrappedComponent) => {
class WithTimer extends React.Component {
constructor(props) {
super(props);
this.state = {
warningTime: 5000,
signoutTime: 10000,
showModal: false,
modalMessage: "",
modalButtonText: "",
};
this.events = [
'load',
'mousemove',
'mousedown',
'click',
'scroll',
'keypress'
];
for (var i in this.events) {
window.addEventListener(this.events[i], this.resetTimeout);
}
this.setTimeout();
}
clearTimeoutFunc = () => {
if (this.warnTimeout) clearTimeout(this.warnTimeout);
if (this.logoutTimeout) clearTimeout(this.logoutTimeout);
};
setTimeout = () => {
this.warnTimeout = setTimeout(this.warn, this.state.warningTime);
this.logoutTimeout = setTimeout(this.logout, this.state.signoutTime);
};
resetTimeout = () => {
this.clearTimeoutFunc();
this.setTimeout();
};
onModalClick = () => {
this.setState({
showModal: false,
}, () => this.resetTimeout())
}
warn = () => {
this.setState({
modalButtonText: "Stay Logged In",
modalHeader: "Warning!",
modalMessage: 'You will be logged out soon. Click to stay logged in.',
showModal: true,
});
};
logout = () => {
this.setState({
modalButtonText: "Ok",
modalHeader: "Session Timed Out",
modalMessage: 'You are being logged out!',
showModal: true,
});
// log the user out here
};
render() {
console.log('HOC');
return (
<>
<MyModal
show={this.state.showModal}
modalMessage={this.state.modalMessage}
modalHeader={this.state.modalHeader}
buttonText={this.state.modalButtonText}
onButtonClick={this.onModalClick} />
<WrappedComponent {...this.props.children} />
</>
);
}
}
return WithTimer;
};
export default withTimer;
MyModal.js
import React, { useState } from "react";
import { Modal, Button } from "react-bootstrap";
function MyModal({ show = false, modalMessage, modalHeader, onButtonClick, buttonText }) {
const handleClick = event => {
onButtonClick(event);
}
return (
<Modal show={show} onHide={handleClick} animation={false}>
<Modal.Header closeButton>
<Modal.Title>{modalHeader}</Modal.Title>
</Modal.Header>
<Modal.Body>{modalMessage}</Modal.Body>
<Modal.Footer>
<Button variant="primary" onClick={handleClick}>
{buttonText}
</Button>
</Modal.Footer>
</Modal>
);
}
export default MyModal;
Yes, you can render any components you'd like in the HOC. So in your case you can render a <Modal/>.
Of course, whether the modal is displayed or not is dynamic, so that's a perfect job for the component's state to come into play. Use conditional statements in your render function to either render or not render your modal.
import React from 'react';
import { Modal } from 'react-bootstrap';
const withTimer = (WrappedComponent) => {
class WithTimer extends React.Component {
constructor(props) {
super(props);
this.state = {
showWarning: false,
showLogout: false,
warningTime: 5000,
signoutTime: 10000
};
this.events = [
'load',
'mousemove',
'mousedown',
'click',
'scroll',
'keypress'
];
for (var i in this.events) {
window.addEventListener(this.events[i], this.resetTimeout);
}
this.setTimeout();
}
clearTimeoutFunc = () => {
if (this.warnTimeout) clearTimeout(this.warnTimeout);
if (this.logoutTimeout) clearTimeout(this.logoutTimeout);
};
setTimeout = () => {
this.warnTimeout = setTimeout(this.warn, this.state.warningTime);
this.logoutTimeout = setTimeout(this.logout, this.state.signoutTime);
};
resetTimeout = () => {
this.clearTimeoutFunc();
this.setTimeout();
};
warn = () => {
this.setState({ showWarning: true });
};
logout = () => {
this.setState({ showLogout: true });
// log the user out here
};
render() {
let modal;
if (this.state.showLogout) {
modal = <Modal>...</Modal>;
} else if (this.state.showWarning) {
modal = <Modal>...</Modal>;
} else {
modal = null;
}
return <React.Fragment>
<WrappedComponent {...this.props.children} />
{ modal }
</React.Fragment>;
}
}
return WithTimer;
};
export default withTimer;

React <Redirect> after transition not working

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 = () => {
...

Issue with react onClick

My app has an onClick that should be rendering more gifs. However, it does it once and then stops. Also, the onClick deletes all the gifs that were already on the page. Anyone know what I'm doing wrong?
class App extends Component {
constructor(props) {
super(props);
this.state = {
results: [],
};
}
componentDidMount() {
this.searchGifs('kittens');
}
searchGifs = (searchQuery) => {
fetch(`http://api.giphy.com/v1/gifs/search?q=${searchQuery}&limit=12&api_key=dc6zaTOxFJmzC`).then(data => data.json())
.then(response => {
this.setState({
results: response.data,
});
});
}
searchMoreGifs = (offsetQuery) => {
fetch(`http://api.giphy.com/v1/gifs/search?q=${offsetQuery}&limit=12&offset=${this.state.results.length}&api_key=dc6zaTOxFJmzC`).then(data => data.json())
.then(response => {
this.setState({
results: response.data,
});
});
}
render() {
return (
<main className="app">
<Header />
<SearchForm startSearch={this.searchGifs} />
<ResultList gifs={this.state.results} />
<LoadMore gifs={this.state.results} searchMore={this.searchMoreGifs} />
</main>
);
}
}
and here is the onClick:
class LoadMore extends React.Component {
render(props) {
return(
<button onClick={this.props.searchMore}>Load More</button>
);
}
}
export default LoadMore;
Each time you call this.setState({results: something}) you completely overwrite the previous state of results.You want to take the array that is in this.state.results and concat it with the new results.
class App extends Component {
constructor(props) {
super(props);
this.state = {
results: [],
// I also suggest moving the searchQuery to the state so it can be used in both the offset and the original search
searchQuery: 'kittens'
};
}
componentDidMount() {
this.searchGifs(this.state.searchQuery);
}
searchGifs = (searchQuery) => {
fetch(`http://api.giphy.com/v1/gifs/search?q=${searchQuery}&limit=12&api_key=dc6zaTOxFJmzC`).then(data => data.json())
.then(response => {
this.setState({
results: response.data,
});
});
}
searchMoreGifs = (offsetQuery) => {
fetch(`http://api.giphy.com/v1/gifs/search?q=${offsetQuery}&limit=12&offset=${this.state.results.length}&api_key=dc6zaTOxFJmzC`).then(data => data.json())
.then(response => {
this.setState({
// You were overwriting the old results with new data every time you ran this function
results: this.state.results.concat(response.data),
});
});
}
render() {
return (
<main className="app">
<Header />
<SearchForm startSearch={this.searchGifs} />
<ResultList gifs={this.state.results} />
{/* You also need to pass the offsetQuery to the LoadMore component so that the searchMore function can use it*/}
<LoadMore searchMore={this.searchMoreGifs} offsetQuery={this.state.searchQuery} />
</main>
);
}
}
class LoadMore extends React.Component {
render(props) {
const {offsetQuery, searchMore} = this.props
return (
<button onClick={() => searchMore(offsetQuery)}>Load More</button>
);
}
}
export default LoadMore;

Categories

Resources