I am trying to use the react.js spinner component and I can't figure out how I to hide it once my task is complete.
Here is a simple codepen where I am using hidden property. I will set it up to false or true depending on if I want to show it or not:
https://codepen.io/manish_shukla01/pen/GLNOyw
<Spinner hidden={true} size={SpinnerSize.large} label="manish's large spinned" />
You need to use conditional rendering to hide/show the spinner. You can define a component state then can set it up to false or true depending on if you want to show it or not.
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
hidden: false
};
}
render() {
return (
<div className="App">
{!this.state.hidden && <SpinnerBasicExample />}
<h2>Start editing to see some magic happen!</h2>
</div>
);
}
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
For more you can read this https://reactjs.org/docs/conditional-rendering.html
Conditional rendering using state.
In React, you can create distinct components that encapsulate behavior you need. Then, you can render only some of them, depending on the state of your application.
Working example (click on the Dashboard tab):
containers/Dashboard
import isEmpty from "lodash/isEmpty";
import React, { Component } from "react";
import api from "../../utils/api";
import DisplayUser from "../../components/DisplayUser";
import DisplaySignUp from "../../components/DisplaySignUp";
import Spinner from "../../components/Spinner";
class Dashboard extends Component {
state = {
isLoading: true,
currentUser: {}
};
componentDidMount = () => {
this.fetchUsers();
};
fetchUsers = async () => {
try {
const res = await api.get(`/users/1`);
setTimeout(() => {
this.setState({ currentUser: res.data, isLoading: false });
}, 1500);
} catch (e) {
console.error(e.toString());
}
};
// the below can be read like so:
// if "isLoading" is true... then display a spinner
// else if "currentUser" is not empty... then display the user details
// else show a signup message
render = () =>
this.state.isLoading ? (
<Spinner />
) : !isEmpty(this.state.currentUser) ? (
<DisplayUser currentUser={this.state.currentUser} />
) : (
<DisplaySignUp />
);
}
export default Dashboard;
For what you intend to do, just adding the hidden prop won't work as that is not an expected attribute of the Spinner component. I think what you need to do is to introduce conditional rendering in your component. Kindly see implementation below:
import * as React from 'react';
import { render } from 'react-dom';
import {
PrimaryButton,
Spinner,
SpinnerSize,
Label,
IStackProps,
Stack
} from 'office-ui-fabric-react';
import './styles.css';
const { useState } = React;
const SpinnerBasicExample: React.StatelessComponent = () => {
// This is just for laying out the label and spinner (spinners don't have to be inside a Stack)
const rowProps: IStackProps = { horizontal: true, verticalAlign: 'center' };
const tokens = {
sectionStack: {
childrenGap: 10
},
spinnerStack: {
childrenGap: 20
}
};
return (
<Stack tokens={tokens.sectionStack}>
<Stack {...rowProps} tokens={tokens.spinnerStack}>
<Label>Extra small spinner</Label>
<Spinner size={SpinnerSize.xSmall} />
</Stack>
<Stack {...rowProps} tokens={tokens.spinnerStack}>
<Label>Small spinner</Label>
<Spinner size={SpinnerSize.small} />
</Stack>
</Stack>
);
};
function App() {
const [hidden, setHidden] = useState(false);
return (
<div className="App">
{hidden && <SpinnerBasicExample />}
<PrimaryButton
data-automation-id="test"
text={!hidden ? 'Show spinner' : 'Hide spinner'}
onClick={() => setHidden(!hidden)}
allowDisabledFocus={true}
/>
</div>
);
}
Related
I'm tasked with using react to create our online store. So far I've succesfully called our products using the data from the API we're developing, and I've also been able to pass the data from the mapped product list to a single product page using a link.
Only issue now is that we'd like the single product to appear on the same page as the product list when it's clicked on by the user, perhaps as a component that appears above the product list (as opposed to linking to a separate page). For the life of me I cannot seem to find a method of doing this that doesn't result in an error or the app reading parameters as undefined. Admitedly, I am quite new to React, so it's possible I'm missing something very obvious.
This is the ProductList.js
import React, { useState, useEffect } from 'react';
import SingleProduct from './SingleProduct';
import { Link } from 'react-router-dom';
const API_URL = "http://exampleapiurl/ExampleCollection/Examplecollectionid";
const Products = () => {
const [products, setProducts] = useState([]);
useEffect(() => {
getProducts().then((products) => setProducts(products))
}, []);
const getProducts = () =>
fetch(API_URL)
.then((res) => res.json());
// OnClick Handler
const [isShown, setIsShown] = useState(false);
const handleClick = (e) => {
setIsShown(current => !current);
};
return (
<div className="GetProducts">
<h1> Fetch Products from a Collection </h1>
<div className="container">
<div className="row">
{/* 👇️ Ideally, we'd like the single product item to appear here on button click, as opposed to a separate page */}
{
isShown &&
<SingleProduct/>
}
{products.map((frame) => (
<div>
{/* 👇️ Current link to separate page for product*/}
<Link to={`/SingleProduct/${frame.frameId}`}>
{/* 👇️ Button to show single item on same page as product list.*/}
<button onClick={handleClick} value={frame.frameId} > View {frame.frameName}</button>
<div key={frame.frameId}>
<img src={`https://exampleimageurl/${frame.thumnail}`} />
<li>Frame Name: {frame.frameName}</li>
<li>Gender: {frame.gender}</li>
</div>
</Link>
</div>
))
}
</div>
</div>
</div>
)
}
export default Products;
This is the SingleProduct.js
class SingleProduct extends React.Component {
constructor(props) {
super(props)
this.state = {
isLoading: false,
item: [],
frameId: this.props.match.params.frameId
}
}
componentDidMount() {
this.setState({ isLoading: true });
fetch(`http://exampleapiurl/${this.state.frameId}`)
.then(response => response.json())
.then(json => {
this.setState({
item: json,
isLoading: false
})
})
}
render() {
const { item } = this.state;
return (
this.state.isLoading ?
(<h1>Loading {this.state.frameId}...</h1>)
:
(
<div>
<div className="col border text-center" key={item.frameId}>
<img src={`https://exampleimageurl/${item.framePic}`} />
<li>Frame Name: {item.frameName}</li>
<li>Gender: {item.gender}</li>
</div>
</div>
)
)
}
}
export default SingleProduct
App.js
import React, { Component } from 'react';
import { Route } from 'react-router';
import { Home } from './components/Home';
import { Layout } from './components/Layout';
import Products from './components/ProductList';
import SingleProduct from './components/SingleProduct';
export default class App extends Component {
static displayName = App.name;
render() {
return (
<Layout>
<Route exact path='/' component={Home} />
<Route path='/ProductList' component={Products} />
<Route path='/SingleProduct/:frameId' component={SingleProduct} />
</Layout>
);
}
}
So if I understand correctly you don't want to use route for passing the data instead of that you can then pass props to the SingleProduct component.
With props getting passed it should look
{
isShown &&
<SingleProduct frameId = {selectedFrameId}/>
}
Declare a new state variable to store the selected frameid
const [selectedFrameId, setSelectedFrameId] = useState<Number>();
Your onclick event will need adjustment, because you will need to pass the frameid in map function.
onClick={() => this.handleClick(frame.frameId)}
and then set the state via handleClick event
const handleClick = (frameId) => {
setIsShown(current => !current);
setSelectedFrameId(frameId);
};
With this in your SingleProduct component the fetch call can directly use the props(frameid)
fetch(`http://exampleapiurl/${this.props.frameId}`)
Also I would recommend to change SingleProduct to a functional component instead of class component.
I've been playing around with the react context api and I'm just not getting why it's not working.
I have a component with a container that should show or hide depending on a valuer stored in context.
This is the component:
import React, { useContext } from 'react';
import ResultsContext from '../../context/results/resultsContext';
const ResultsPanelContainer = () => {
const resultsContext = useContext(ResultsContext);
const { showResults } = resultsContext;
console.log('showResults in ResultsPanelConatiner: ', showResults);
return (
<div
className='container-fluid panel'
style={{ display: showResults ? 'block' : 'none' }}
>
<div className='container'>
<div className='row'>
<div className='col'>
<h1 className='display-4'>Results.Panel.js</h1>
</div>
</div>
</div>
</div>
);
};
export default ResultsPanelContainer;
For completeness, the context is divided up into three sections, the call to the context itself, a 'state' file and a reducer. These are displayed below:
resultsContext.js
import { createContext } from 'react';
const resultsContext = createContext();
export default resultsContext;
ResultsState.js
import React, { useReducer } from 'react';
// import axios from 'axios';
import ResultsContext from './resultsContext';
import ResultsReducer from './resultsReducer';
import { UPDATE_SHOW_RESULTS } from '../types';
const ResultsState = (props) => {
const initialState = {
showResults: false,
};
const [state, dispatch] = useReducer(ResultsReducer, initialState);
const updateShowResults = (data) => {
console.log('updateShowResults - ', data);
dispatch({
type: UPDATE_SHOW_RESULTS,
payload: data,
});
};
return (
<ResultsContext.Provider
value={{
showResults: state.showResults,
updateShowResults,
}}
>
{props.children}
</ResultsContext.Provider>
);
};
export default ResultsState;
resultsReducer.js
import { UPDATE_SHOW_RESULTS } from '../types';
export default (state, action) => {
switch (action.type) {
case UPDATE_SHOW_RESULTS:
return {
...state,
showResults: action.payload,
};
default:
return state;
}
};
The change is triggered by a button click in a separate component and this does trigger an update in the context as shown when you log it to the console. However, the component is not rerendering.
I understand from reading various answers on here that changing context doesn't trigger a rerender of all child components in the same way that setState does. However, the component displaying this is calling the context directly so as far as I can see the rerender should take effect.
Am I missing something glaringly obvious?
Thanks in advance.
Stef
Forget the above... I'm an idiot - wrapped the two separate parts of the app in two separate instances of ResultsState which weren't communicating. Did this:
const App = () => {
return (
<Fragment>
<UsedDataState>
<Header />
</UsedDataState>
<main>
<ExportPanelContainer />
<ResultsState>
<SendQueryState>
<OrQueryState>
<AndQueryState>
<QueryPanelContainer />
</AndQueryState>
</OrQueryState>
</SendQueryState>
</ResultsState>
<ResultsState>
<ResultsPanelContainer />
</ResultsState>
</main>
</Fragment>
);
};
Instead of this:
const App = () => {
return (
<Fragment>
<UsedDataState>
<Header />
</UsedDataState>
<main>
<ExportPanelContainer />
<ResultsState>
<SendQueryState>
<OrQueryState>
<AndQueryState>
<QueryPanelContainer />
</AndQueryState>
</OrQueryState>
</SendQueryState>
<ResultsPanelContainer />
</ResultsState>
</main>
</Fragment>
);
};
Hope this is useful for someone else...
I am trying to Redirect or Switch the following Hero React component after a 5 second delay using React-Router.
<Redirect to="/home" /> redirect's the component to http://url.com/home instantly.
Any suggestions on how to handle this? Thx.
class Intro extends Component {
render() {
return (
<div className="d-flex w-100 h-100 mx-auto p-4 flex-column">
<Header />
<div
style={{
paddingTop: '40px'
}}>
</div>
<Hero />
<Footer />
</div>
);
}
}
export default Intro;
I've also tried using setTimeout as well
You can use the history prop injected by react-router and invoke the push method inside a setTimeout. You should do it in componentDidMount:
constructor(props) {
// ...
this.redirectTimeout = null;
}
componentDidMount() {
const { history } = this.props;
this.redirectTimeout = setTimeout(() => {
history.push('/home')
}, 5000);
}
Edit: Make sure to clear the timeout to avoid redirects when you move out of the page before the 5 seconds are over, else you will still get redirected:
componentWillUnmount() {
clearTimeout(this.redirectTimeout);
}
You can create a custom DelayRedirect component.
Gist here: https://gist.github.com/JT501/4b0f60fb57b1410604b754fd9031150a
import * as React from 'react';
import { useEffect, useState } from 'react';
import { Redirect, RedirectProps } from 'react-router';
interface DelayProps {
delay: number;
}
const DelayRedirect = ({ delay, ...rest }: RedirectProps & DelayProps) => {
const [timeToRedirect, setTimeToRedirect] = useState(false);
useEffect(() => {
const timeout = setTimeout(() => {
setTimeToRedirect(true);
}, delay);
return () => clearTimeout(timeout);
}, [delay]);
return timeToRedirect ? <Redirect {...rest} /> : null;
};
export default DelayRedirect;
Usage:
<DelayRedirect to={'/'} delay={1000} />
could anyone tell me why is that won't work? Proper data is displaying in the console (console.log(this.state);), but it won't be transfered to MainContainer.
Same data initialized in the constructor>state>users working without issues. Where's the problem?
App
import React, {Component} from 'react';
import logo from './logo.svg';
import './App.css';
import Header from './components/header/Header';
import MainContainer from './containers/main-container/MainContainer';
class App extends Component {
constructor(props) {
super(props);
this.state = {
users: []
}
}
componentDidMount() {
fetch('https://jsonplaceholder.typicode.com/users')
.then(response => response.json())
.then(users => {
let u = users.map((user) => {
return {id: user.id, name: user.name, email: user.email}
})
return u;
})
.then(u => {
this.setState({users: u});
console.log(this.state);
});
}
render() {
return (
<div className="App">
<Header/>
<MainContainer users={this.state.users}/>
</div>
)
}
}
export default App;
MainContainer
import React from 'react';
import ActionBar from '../../components/action-bar/ActionBar'
import ListHeader from '../../components/list-header/ListHeader'
import ListItem from '../../components/list-item/ListItem'
import ListItemPlaceholder from '../../components/list-item-placeholder/ListItemPlaceholder'
class MainContainer extends React.Component {
constructor(props) {
super(props);
this.state = {
users : props.users
}
}
render() {
const list = this.state.users.map(
(user) =>
{
const liStyle = {
'background-color': user % 2 == 0 ? '#fbfcfc' : 'transparent',
};
return <ListItem key={user.id} style={liStyle} id={user.id} name={user.name} email={user.email}/>
}
);
return (
<div className={'main-container'}>
<ActionBar />
<ListHeader />
{list}
</div>
)
}
}
export default MainContainer;
.................................................................................................................
Best Regards!
crova
In your <MainContainer> component you store the users in its state in the constructor but you never alter it. You only need to use state when the component needs to alter it during its lifetime. But the users come from it's parent via the users prop which you never render. So just render that prop instead:
const MainContainer = props => (
<div className="main-container">
<ActionBar />
<ListHeader />
{props.users.map(({id, name, email}) => (
<ListItem
key={id}
style={{
backgroundColor: id % 2 === 0 ? '#fbfcfc' : 'transparent'
}}
id={id}
name={name}
email={email}
/>
))}
</div>
);
When the users change in the parent it will re-render and pass the new users array to the <MainContainer>.
Also note that if your component only renders props and has no own state it can be written as a stateless functional component.
I am using react with MUI framework and I was wondering how can I create an loading button using this framework?
I am looking for something similar to this.
To the best of my knowledge, there is no single component that accomplishes this out of the box in material-ui. However, you can implement your own easily using CircularProgress.
Assuming you are using material-ui v1, here's a rough example. First, I create a LoadingButton that accepts a loading prop - if that prop is true, I display a CircularProgress indicator. It also accepts a done prop - if that's true, the button clears the progress indicator and becomes a checkmark to show success.
import React from 'react';
import PropTypes from 'prop-types';
import { withStyles } from 'material-ui/styles';
import Button from 'material-ui/Button';
import { CircularProgress } from 'material-ui/Progress';
import Check from 'material-ui-icons/Check';
const styles = theme => ({
button: {
margin: theme.spacing.unit,
},
});
const LoadingButton = (props) => {
const { classes, loading, done, ...other } = props;
if (done) {
return (
<Button className={classes.button} {...other} disabled>
<Check />
</Button>
);
}
else if (loading) {
return (
<Button className={classes.button} {...other}>
<CircularProgress />
</Button>
);
} else {
return (
<Button className={classes.button} {...other} />
);
}
}
LoadingButton.defaultProps = {
loading: false,
done: false,
};
LoadingButton.propTypes = {
classes: PropTypes.object.isRequired,
loading: PropTypes.bool,
done: PropTypes.bool,
};
export default withStyles(styles)(LoadingButton);
You can use the LoadingButton as shown in the following example, which uses state to set the appropriate prop on the button.
import React from 'react';
import LoadingButton from './LoadingButton';
class ControlledButton extends React.Component {
constructor(props) {
super(props);
this.state = { loading: false, finished: false };
}
render() {
const { loading, finished } = this.state;
const setLoading = !finished && loading;
return (
<div>
<LoadingButton
loading={setLoading}
done={finished}
onClick={() => {
// Clicked, so show the progress dialog
this.setState({ loading: true });
// In a 1.5 seconds, end the progress to show that it's done
setTimeout(() => { this.setState({ finished: true })}, 1500);
}}
>
Click Me
</LoadingButton>
</div>
);
}
}
export default ControlledButton;
You can of course tweak the styling and functionality to meet your exact needs.
In the newer versions of MUI, you can use LoadingButton component, it's currently in the lab package. This is just a wrapper of the Button with a loading prop. You can customize the loadingIndicator component and its position. See the example below:
import LoadingButton from '#mui/lab/LoadingButton';
<LoadingButton loading={loading}>
Text
</LoadingButton>
<LoadingButton
endIcon={<SendIcon />}
loading={loading}
loadingPosition="end"
variant="contained"
>
Send
</LoadingButton>