I use inView in my component. Until the posts were loaded, the user see the skeleton. But when I press delete button, the skeleton appears and dissappers. Can I stop this quick appearance when press the delete button.
class MyPosts extends Component {
deleteItem = (key) => {
this.props.deleteItem(key);
};
render() {
const { myPosts, classes } = this.props;
let posts = myPosts.map((item) => {
return (
<InView threshold={0}>
{({ ref, inView }) => (
<div ref={ref} inView={inView}>
{inView ? (
<Card>
<Typography >
{item.post}
</Typography>
<Button
onClick={() => this.deleteItem(item.key)}
>
Done
</Button>
</Card>
) : (
<Skeleton />
)}
</div>
)}
</InView>
);
});
return <div>{posts}</div>;
}
}
Related
The below code adds a next button to get the next 20 items from my backend, on clicking the button the data changes and I get my next 20 items, but the url does not change.
function PokemonList() {
const classes = useStyles();
let [pageNum, setPageNum] = useState(0);
const { loading, error, data } = useQuery(pokemonList, { variables: { pageNum: pageNum } });
function handleClick(e){
e.preventDefault();
setPageNum(parseInt(pageNum)+1)
}
if(error) {
return <h1> error</h1>;
}
if(loading) {
return <h1> loading</h1>;
}
return (
<div className="App">
{data.pokemonList.map((data) => (
<Card className={classes.card} variant='outlined'>
<CardHeader className={classes.titleHead} title={data.id} />
<CardMedia
className={classes.media}
component='img'
image={data.url}
title='image'
/>
<CardContent>
<Typography variant='body2' color='textSecondary' component='span'>
<p>{data.name}</p>
<br/>
<br/>
<br></br>
</Typography>
</CardContent>
</Card>
))}
<Link onClick={handleClick} className='characterlink2' to={`/pokemon/page/${parseInt(pageNum)+1}`}>
<button>
Next
</button>
</Link>
</div>
);
}
export default PokemonList;
How can I fix this? I am not sure that the "to" and "onClick" work together. How do I change the url along with the data?
Issue
e.preventDefault(); in the click handler prevents the default navigation action from occurring.
Solution
I don't see any reason for this action to be prevented, so I suggest removing this call to prevent the default action.
function handleClick(e){
setPageNum(page => page + 1);
}
Preferred solution
Assuming you've a route with path="/pokemon/page/:page" you should use the useParams hook and "sniff" the current page. This completely eliminates the need to synchronize the URL path and local React state, there's only one source of truth, the URL path.
import { useParams } from 'react-router-dom';
...
function PokemonList() {
const classes = useStyles();
const { page } = useParams();
const { loading, error, data } = useQuery(
pokemonList,
{ variables: { pageNum: page } },
);
if (error) {
return <h1>error</h1>;
}
if (loading) {
return <h1>loading</h1>;
}
return (
<div className="App">
{data.pokemonList.map((data) => (
...
))}
<Link
className='characterlink2'
to={`/pokemon/page/${Number(page) + 1}`}
>
<button type="button">Next</button>
</Link>
</div>
);
}
I am new in react, i am trying to get component based on if button is clicked and once the component is returned to Ui, i want to change button state back to original(false),so if i click on that button again i should get again new component below to the previous one,and this should just go on..,
in below code i am able to get this just one time.
const Command = () => {
const [buttonState, setButtonState] = useState(false);
const AndOrHandler = () => {
if (buttonState) {
return (
<div>
<Grid container>
<SomeComponent />
</Grid>
</div>
);
}
return null;
};
return (
<Fragment>
<FormControl>
<SomeComponent />
<AndOrHandler />
<Button onClick={() => setButtonState(true)} variant="contained" color="primary">Add Condition</Button>
</FormControl>
</Fragment>
);
};
export default Command;
You want continue adding component, i suggest have state components:
const Command = () => {
const component = <div>
<Grid container>
<SomeComponent />
</Grid>
</div>;
const [components, setComponents] = useState([]);
return (
<Fragment>
<FormControl>
<SomeComponent />
{components.map(component => component)}
<Button onClick={() => setComponents([...components, component])} variant="contained" color="primary">Add Condition</Button>
</FormControl>
</Fragment>
);
};
export default Command;
I have a nav menu built with material-ui/core in Navbar.
I use useRef to track the position of clicked button on toggle menu close.
anchorRef.current.contains(event.target)
And I am getting 'Uncaught TypeError: anchorRef.current.contains is not a function' .
I tried 'Object.values(anchorRef.current).includes(event.target)' instead, it always returns false.
-- update --
anchorRef.current.props Object.
withStyles {
props:{
aria-haspopup: "true"
aria-owns: undefined
children: "계정"
className: "nav-menu--btn"
onClic: f onClick()
get ref: f()
isReactWarning: true
arguments: (...)
caller: (...)
length: 0
name: "warnAboutAccessingRef"
...
}, context{...}, refs{...}, ...}
ToggleMenuList
const ToggleMenuList = ({ navAdminList, navAdminItems, classes }) => {
const [activeId, setActiveId] = useState(null);
const anchorRef = useRef(null);
const handleToggle = id => {
setActiveId(id);
};
const handleClose = event => {
if (anchorRef.current && anchorRef.current.contains(event.target)) {
return;
}
setActiveId(null);
};
return (
<React.Fragment>
<div className={`nav-menu--admin ${classes.root}`}>
{navAdminList.map(e => (
<div key={e.id}>
<Button
ref={anchorRef}
aria-owns={activeId === e.id ? 'menu-list-grow' : undefined}
aria-haspopup="true"
onClick={() => handleToggle(e.id)}
>
{e.name}
</Button>
{activeId === e.id && (
<ToggleMenuItems
id={e.id}
activeId={activeId}
handleClose={handleClose}
anchorRef={anchorRef}
items={navAdminItems[e.id]}
/>
)}
</div>
))}
</div>
</React.Fragment>
);
};
export default withStyles(styles)(ToggleMenuList);
ToggleMenuItems
const ToggleMenuItems = ({
listId,
activeId,
handleClose,
anchorRef,
items,
}) => {
const isOpen = activeId === listId;
const leftSideMenu = activeId === 3 || activeId === 4 ? 'leftSideMenu' : '';
return (
<Popper
open={isOpen}
anchorEl={anchorRef.current}
keepMounted
transition
disablePortal
>
{({ TransitionProps, placement }) => (
<Grow
{...TransitionProps}
style={{
transformOrigin:
placement === 'bottom' ? 'center top' : 'center bottom',
}}
className={`toggle-menu ${leftSideMenu}`}
>
<Paper id="menu-list-grow">
<ClickAwayListener
onClickAway={handleClose}
>
<MenuList className="toggle-menu--list">
{items.map(e => (
<MenuItem
key={e.id}
className="toggle-menu--item"
onClick={handleClose}
>
<Link
to={e.to}
className="anchor td-none c-text1 toggle-menu--link"
>
{e.name}
</Link>
</MenuItem>
))}
</MenuList>
</ClickAwayListener>
</Paper>
</Grow>
)}
</Popper>
);
};
export default ToggleMenuItems;
react: ^16.8.6
react-dom: ^16.8.6
react-router-dom: ^4.3.1
#material-ui/core: ^3.1.2
I assume your ToggleMenuItems sets up global(document-level?) event listener on click to collapse Menu on clicking somewhere outside.
And you have a sibling button element. Clicking on that you want to keep menu expanded, right? So that was the point to use .contains in onClick to check if we are clicked outside of ToggleMenuItems but in scope of specific Button. The reason why it does not work: <Button> is custom class-based React component so it returns React component instance in ref. And it does not have any DOM-specific methods like .contains
You can rework you current approach: just stop bubbling event in case Button has been clicked. It would stop global event handler set by ToggleMenuItems to react.
const stopPropagation = (event) => event.stopPropagation();
const ToggleMenuList = ({ navAdminList, navAdminItems, classes }) => {
const [activeId, setActiveId] = useState(null);
const anchorRef = useRef(null);
const handleToggle = id => {
setActiveId(id);
};
const handleClose = event => {
setActiveId(null);
};
return (
<React.Fragment>
<div className={`nav-menu--admin ${classes.root}`}>
{navAdminList.map(e => (
<div key={e.id}>
<div onClick={stopPropagation}>
<Button
aria-owns={activeId === e.id ? 'menu-list-grow' : undefined}
aria-haspopup="true"
onClick={() => handleToggle(e.id)}
>
{e.name}
</Button>
</div>
{activeId === e.id && (
<ToggleMenuItems
id={e.id}
activeId={activeId}
handleClose={handleClose}
anchorRef={anchorRef}
items={navAdminItems[e.id]}
/>
)}
</div>
))}
</div>
</React.Fragment>
);
};
export default withStyles(styles)(ToggleMenuList);
I've put stopPropagation handler outside since it does not depend on any internal variable.
i have a question . I have to build an app where people can search for music from "lastFm" . So far so good , i already made few things to works normal , but i have a problem with if/else in map function , i've try to show user "no result found" if there are any , but with no luck .If there is 1+ results , will be displayed on the screen , but if there are any , nothing happen . Here is my code .
import React, { Component } from 'react';
import AppBar from '#material-ui/core/AppBar';
import Toolbar from '#material-ui/core/Toolbar';
import Typography from '#material-ui/core/Typography';
import {
TextField,
Button,
List,
ListItem,
ListItemAvatar,
ListItemText,
Avatar,
Card,
CardContent
} from '#material-ui/core';
import axios from 'axios';
import './App.css';
const API_URL = 'http://ws.audioscrobbler.com/2.0/?limit=5&format=json&method=artist.search&api_key=' + process.env.REACT_APP_LASTFM_APPKEY;
const isEmpty = (str) => str.length === 0;
class App extends Component {
state = {
searchTerm: '',
savedArtists: []
}
componentDidMount() {
const existing = localStorage.getItem('savedArtists')
if (existing) {
this.setState({ savedArtists: JSON.parse(existing) })
}
}
onTextChange = (event) => {
const value = event.target.value;
this.setState({ searchTerm: value });
}
search = (terms) => {
const request = API_URL + '&artist=' + terms;
axios.get(request).then((response) => {
const results = response.data.results;
const artists = results.artistmatches.artist.map((artist) => {
const avatarImage = artist.image.find(image => image.size === 'medium');
const imageUrl = avatarImage['#text'];
return { ...artist, avatar: imageUrl }
});
this.setState({ artists });
})
}
onSearchClick = () => {
this.search(this.state.searchTerm);
}
clearSearch = () => {
this.setState({
searchTerm: '',
artists: []
})
}
onResultClick = (artist) => {
this.clearSearch();
const savedArtists = this.state.savedArtists;
savedArtists.push(artist)
this.setState({ savedArtists: savedArtists })
localStorage.setItem('savedArtists', JSON.stringify(savedArtists));
}
render() {
const results = this.state.artists || [];
return (
<div className="App">
<header className="App-header">
<AppBar position="static" color="primary">
<Toolbar className="search-bar">
<Typography variant="h6" color="inherit">
Photos
</Typography>
<TextField
placeholder="Search on Last.fm"
className="search-input"
onChange={this.onTextChange}
value={this.state.searchTerm}
/>
<Button
onClick={this.onSearchClick}
variant="contained"
color="secondary"
disabled={isEmpty(this.state.searchTerm)}
>
Search
</Button>
{!isEmpty(this.state.searchTerm) && (
<Button
onClick={this.clearSearch}
variant="contained"
>
Clear
</Button>)
}
</Toolbar>
</AppBar>
</header>
//****Here is where i've try to use if/else
<List className="search-results">
{
results.map((artist ,results) => {
if(results.length === 0)
return (<ListItem> Not Found</ListItem>
); else {
return ( <ListItem
button
key={artist.name}
className="result"
onClick={() => this.onResultClick(artist)}
>
<ListItemAvatar>
<Avatar src={artist.avatar} alt={artist.name} />
</ListItemAvatar>
<ListItemText primary={artist.name} />
<Button
variant="outlined"
color="secondary"
size="small"
className="add-button"
>
Add to favorites
</Button>
</ListItem>);
}
})
}
</List>
<div className="artist-container">
{
this.state.savedArtists.map((artist, i) => {
return (
<Card className="artist-card"
key={i}
>
<CardContent>
{artist.name}
</CardContent>
</Card>
)
})
}
</div>
</div>
);
}
}
export default App;
You're having an error there. It's .map(result: any, index: number, original: []), so you're referring to an index number with argument results:
results.map((artist, results) => {
if(results.length === 0) { ... }
});
So fix it just by not referring to results as a argument of .map
The problem is that you're trying to do an if/else in the map of the array. But if the array has no items, then there is nothing to map.
What to do is to use a ternary to check if the array has any results:
{ results && result.length ?
<List className="search-results">
{
results.map((artist) => {
return (
<ListItem button key={artist.name} className="result" onClick={() => this.onResultClick(artist)} >
<ListItemAvatar>
<Avatar src={artist.avatar} alt={artist.name} />
</ListItemAvatar>
<ListItemText primary={artist.name} />
<Button
variant="outlined"
color="secondary"
size="small"
className="add-button"
>
Add to favorites
</Button>
</ListItem>
);
})
}
</List>
: <div>No Results</div>
}
Here, we're checking if results.length is considered truthy or not, if it's 1 or higher, then it will render your list, otherwise it will render our div informing the user there is no results, which you can change out to be whatever you want.
Here is my current code
class cart extends Component {
state = { loading: [] };
addToCart = (e, id) => {
let loading = this.state.loading.slice();
loading[e] = true;
this.setState({
loading,
})
};
render() {
const { data } = this.props;
return (
<div>
{data.map(catering => {
const { menus } = catering;
return (
<Row>
{menus.map(menu => (
<Col xs={12} md={12} lg={6} key={menu.id} className="m-bottom-15">
<Card style={{ height: '165px', border: 0, overflow: 'hidden' }}>
<CardActions>
<LoadingButton
className="button small primary pull-right"
loading={thi.state.loading || false}
onClick={e => this.addToCart(e, menu.id)}
>
<MdAdd size={18} color={white} />
<span className="font-14 font-400">Add to cart</span>
</LoadingButton>
</CardActions>
</Card>
</Col>
))}
</Row>
);
}
}
There will be around 20 button when the map function is done.
What I want to achieve is: every time users click add to cart button, I will call the ajax to save the cart and show loading for the specific clicked button.
After ajax is done, return the state back to normal.
On my current code I haven't put my ajax call yet, I still want to make sure the loading button work on press. Right now its not working.
How can I achieve this?
Thanks.
There is some error in your addToCart method. You should use id as index of loading and set loading array to state as this:
addToCart = (e, id) => {
let loading = this.state.loading.slice();
loading[id] = true;
this.setState({
loading: loading
});
};
Also, in your render method, change this.state.loading to this.state.loading[menu.id]:
<LoadingButton
className="button small primary pull-right"
loading={this.state.loading[menu.id] || false}
onClick={e => this.addToCart(e, menu.id)}
>
When ajax call is done, you just call setState function which sets loading array values to false in callback method.
Instead of maintaining bool in loading array, maintain the menu ids, whenever Add button is clicked, push the id of that item into loading array. When generating the card check whether loading array have that id or not, if yes then show loading otherwise show the card.
Write it like this:
class cart extends Component {
state = { loading: []};
addToCart = (e, id) => {
let loading = this.state.loading.slice();
loading.push(id);
this.setState({
loading
})
};
render() {
const { data } = this.props;
return (
<div>
{data.map((catering,i) => {
const { menus } = catering;
return (
<Row>
{menus.map(menu => {
return this.state.loading.indexOf(menu.id) >= 0 ?
<Col xs={12} md={12} lg={6} key={menu.id} className="m-bottom-15">
<Card style={{ height: '165px', border: 0, overflow: 'hidden' }}>
<CardActions>
<LoadingButton
className="button small primary pull-right"
loading={thi.state.loading || false}
onClick={e => this.addToCart(e, menu.id)}
>
<MdAdd size={18} color={white} />
<span className="font-14 font-400">Add to cart</span>
</LoadingButton>
</CardActions>
</Card>
</Col>
:
/*Loading icon*/
})}
</Row>
)
})}
)
}
}
for the hooks guys this is what I did
const [isLoading, setIsLoading] = useState([]);
<button
type="button"
disabled={isLoading[item.username]}
className="btn btn-success btn-rounded btn-sm my-0"
onClick={()=>activateOrDeactivate( item.activated,item.username)}
> {isLoading[item.username] && <i className="fa fa-circle-o-notch fa-spin"></i>}
{isLoading[item.username]&& <span> Activating...</span>}
{!isLoading[item.username]&& <span>Activate</span>}</button>
const activateOrDeactivate = (valTrueOrFalse,newusername) => {
let loading = isLoading.slice();
loading[newusername] = true;
setIsLoading(loading)
let config = {
headers: {
Authorization: "Bearer " + state.token
},
};
api.post(
"process-activation",
{
activated: !valTrueOrFalse,
username:newusername
},
config
).then(
response => {
getAllUsers()
let loading = isLoading.slice();
loading[newusername] = false;
setIsLoading(loading)