should I use state in addling a new component on click React? - javascript

I am new to react and trying to refactor some es6 js code in to react I have a component that I need to create once I click on an icon similar to insert adjacent html is vanilla js any idea how can I achieve this.
import React, {useState} from 'react'
import Item from './Item';
import { icon, library } from '#fortawesome/fontawesome-svg-core';
import { FontAwesomeIcon } from '#fortawesome/react-fontawesome';
import { faPlusCircle} from '#fortawesome/free-solid-svg-icons';
library.add(faPlusCircle)
function row(props) {
const [item] = useState(<Item />)
return (
<ul className="global">
item
<FontAwesomeIcon onClick={()=>{return <ChangeableItem/>}} con={["fas", "plus-circle"]}/>
<ul/>
)
}

This doesn't do anything:
onClick={()=>{return <ChangeableItem/>}}
The click handler isn't expecting a returned React node and won't do anything with it.
should I use state
Yes. Track in state whether or not this component is displayed. For example:
const [showChangeableItem, setShowChangeableItem] = useState(false);
The state now says not to show the "changeable item". Within the rendering, conditionally render that element based on state. For example:
{ showChangeableItem ? <ChangeableItem/> : null }
Then in your click handler you'd just update the state:
<FontAwesomeIcon onClick={() => setShowChangeableItem(true)} con={["fas", "plus-circle"]}/>
Basically, don't think of it as trying to add elements. All of the elements you need should already be specified in the render, some can just be wrapped in conditional logic. State drives the rendering. Events update state.

You would use a state variable for that.
import React, {useState} from 'react'
import Item from './Item';
import { icon, library } from '#fortawesome/fontawesome-svg-core';
import { FontAwesomeIcon } from '#fortawesome/react-fontawesome';
import { faPlusCircle} from '#fortawesome/free-solid-svg-icons';
library.add(faPlusCircle)
function row(props) {
const [showItem, set_showItem] = useState
return (
<> // use react fragment here, otherwise you will get an error if you try to return several React Elements
<ul className="global">
{showItem? <Item> : null} // return the Item if you want to show it, otherwise return null
<FontAwesomeIcon
onClick={()=> set_showItem(true)}
con={["fas", "plus-circle"]}
/>
<ul/>
</> //ent of React fragment
)
}

Related

Finding the buttons on the screen that have no text for the test

I am trying to write the tests for the NavBar component (using react-native-testing-library) that has several buttons that are basically just icons (using ui-kitten for react native). So I can't get these buttons by text (as there is none) but other methods didn't work for me either (like adding accesibilityLabel or testID and then getting by the label text / getting by test ID). Any ideas what I am doing wrong?
// NavBar.tsx
import React from 'react';
import {View, StyleSheet} from 'react-native';
import {HomeBtn, SaveBtn} from '../components/buttons';
import UserSignOut from './UserSignOut';
const NavBar = ({
navigation,
pressHandlers,
}) => {
return (
<View style={styles.navBar}>
<View>
<HomeBtn navigation={navigation} />
<SaveBtn pressHandler={pressHandlers?.saveBtn ?? undefined} />
</View>
<UserSignOut />
</View>
);
};
export default NavBar;
// HomeBtn.tsx
import React from 'react';
import {Button} from '#ui-kitten/components';
import {HomeIcon} from '../shared/icons';
import styles from './Btn.style';
export const HomeBtn = ({navigation}: any) => {
return (
<Button
accesibilityLabel="home button"
style={styles.button}
accessoryLeft={props => HomeIcon(props, styles.icon)}
onPress={() => navigation.navigate('Home')}
/>
);
};
// NavBar.test.tsx
import React from 'react';
import {render, screen} from '#testing-library/react-native';
import * as eva from '#eva-design/eva';
import {RootSiblingParent} from 'react-native-root-siblings';
import {EvaIconsPack} from '#ui-kitten/eva-icons';
import {ApplicationProvider, IconRegistry} from '#ui-kitten/components';
import NavBar from '../../containers/NavBar';
describe('NavBar', () => {
const navBarContainer = (
<RootSiblingParent>
<IconRegistry icons={EvaIconsPack} />
<ApplicationProvider {...eva} theme={eva.light}>
<NavBar />
</ApplicationProvider>
</RootSiblingParent>
);
it('should render the buttons', async () => {
render(navBarContainer);
// this test fails (nothing is found with this accesibility label)
await screen.findByLabelText('home button');
});
});
Query predicate
The recommended solution would be to use:
getByRole('button', { name: "home button" })
As it will require both the button role, as well as check accessibilityLabel with name option.
Alternative, but slightly less expressive way would be to use:
getByLabelText('home button')
This query will only check accessibilityLabel prop, which also should work fine.
Why is query not matching
Since you're asking why the query is not working, that depends on your test setup. It seems that you should be able to use sync getBy* query and do not need to await findBy* query, as the HomeBtn should be rendered without waiting for any async action.
What might prevent that test from working could be incorrect mocking of any of the wrapping components: RootSiblingParent, ApplicationProvider, they might be "consuming" children prop without rendering it. In order to diagnose the issue you can use debug() function from RNTL to inspect the current state of rendered components. You can also run your tests on render(<NavBar />) to verify that.
Does await screen.findByA11yLabel('home button') work? It should match the accessibilityLabel prop.

onHover event on material ui component is not rendering icon

I am currently trying to render a delete icon on an Material UI component, to be more specific, the MenuItem component. I have created a state field in the component's state and set it to false(this.state.isHovering). The idea is that when I hover over the account item, I want to render the delete icon, and when I hover off, it is supposed to go away. I impleneted these event listeners using onMouseEnter and onMouseLeave and attached them over to the MenuItem. I then made it so that the icon only shows when this.state.isHovering is true. However, when I hover over the MenuItem, it does not render. However, if I just implemented the icon without having the state get involved, it renders. I also console logged in the event handlers to make sure that the event is being registered and it does console log both when hovering in and out. Please see my code. Thanks!
handleMouseEnter = () => {
this.setState({
isHovering: true,
})
}
handleMouseLeave = () => {
this.setState({
isHovering: true,
})
}
I think there are a few issues with your code:
the state is never defined
you don't properly update the state
You don't properly conditionally check/use the state
Lots of bugs in the code you provided (looks like an incomplete copy paste)
You can use reactHooks to help define your state and setState functions
This is my best answer, but the code you provided is missing the account variable and a lot of the referenced functions, so it is incomplete. But it should help with your state issues
import React, { Component, Fragment, useState } from 'react';
import { Typography, withStyles } from '#material-ui/core';
import PropTypes from 'prop-types';
import Paper from '#material-ui/core/Paper';
import Grid from '#material-ui/core/Grid';
import Table from '#material-ui/core/Table';
import TableBody from '#material-ui/core/TableBody';
import TableCell from '#material-ui/core/TableCell';
import MenuItem from '#material-ui/core/MenuItem';
import Icon from '#material-ui/core/Icon';
import AccountTypeIcon from './AccountTypeIcon';
class PortDashSideBar extends Component {
const [isHovering, setIsHovering] = useState(false);
handleMouseEnter = () => {
setIsHovering(true);
}
handleMouseLeave = () => {
setIsHovering(false);
}
return <MenuItem
onMouseEnter={handleMouseEnter}
onMouseLeave={handleMouseLeave}
className={account.id === currentAccountId ? classes.selectedAccount : classes.unselectedAccount}
hover
name={account.name}
key={account.id}
onClick={ () => this.setCurrentAccount(account.id) }>
<TableCell className={classes.cellAccountIcon}>
<AccountTypeIcon type={this.getAccountById(account.id).type} />
</TableCell>
<TableCell className={classes.cellName}>
<Typography className={classes.accountName}>
{account.label}
</Typography>
<Typography>
<span>
{this.getHoldingsForAccount(account.id)}
</span>
{
isHovering ? <Icon>delete</Icon> : null
}
</Typography>
</TableCell>
</MenuItem>;
}

How do I properly use keys with React functional components? Here's my like button:

I'm trying build a Facebook-esque like button with React to get a better handle on stateful components. I'm not sure what the problem is, but I think it's the keys.
Error: Objects are not valid as a React child (found: object with keys {numLikes, onSelect}). If you meant to render a collection of children, use an array instead.
in p (at LikeButton.js:8)
in LikeButton (at App.js:10)
in App (at src/index.js:9)
in StrictMode (at src/index.js:8)
Here's App.js:
import React, { useState } from 'react';
import './App.css';
import LikeButton from './components/LikeButton';
function App() {
const [likes, updateLikes] = useState(23);
const [liked, updateLiked] = useState(false);
return (
<LikeButton
secret='like-button'
numLikes={likes}
// status={liked}
onSelect={(liked) => {
if (liked) {
updateLikes(likes + 1);
} else { updateLikes(likes - 1)
};
updateLiked(!liked);
}
}
/>
// onClick function here, or in LikeButton.js?
);
}
export default App;
Here's LikeButton.js:
import React from 'react';
import FaThumbsUp from 'react-icons/fa';
export default function LikeButton(secret, numLikes, onSelect) {
return (
<>
<div key={secret} onClick={onSelect}>Like Button</div>
<p>{numLikes}</p>
</>
);
}
When using properties in functional component you need to destruct the props, not take them individually. Because properties of a component are the first parameter in the function
import React from 'react';
import FaThumbsUp from 'react-icons/fa';
export default function LikeButton({secret, numLikes, onSelect}) {
return (
<>
<div key={secret} onClick={() => onSelect(true)}>Like Button</div>
<div key={secret} onClick={() => onSelect(false)}>Dislike Button</div>
<p>{numLikes}</p>
</>
);
}
When you get a error of this type, know that you are trying to render a property or variable that is not a React element or non-object type data.
secret, numLikes, onSelect are inside of props object. you should destruct before use.
const {secret, numLikes, onSelect} = props

Material UI v0 ListItem Alignment Issue when using Custom Avatar Component

Material UI version: v0.20.0
I am trying to assign leftAvatar value via CustomAvatar component but it is not aligning as you can see in attached screenshot. Please help.
CustomAvatar: This component is working on some condition bases like if image is available the its
MemberList/index.js
import React, {Component} from 'react';
import {withRouter} from 'react-router-dom';
import MuiThemeProvider from 'material-ui/styles/MuiThemeProvider';
import {List, ListItem} from 'material-ui/List';
import IconMenu from 'material-ui/IconMenu';
import MenuItem from 'material-ui/MenuItem';
import CustomAvatar from 'routes/CustomAvatar';
class MemberList extends Component {
render(){<MuiThemeProvider>
<List>
<ListItem
leftAvatar={<CustomAvatar avatarPic={false}/>}
primaryText={"Mike Tailor"}
secondaryText={"This is first text"}
secondaryTextLines={1}
rightIconButton={<IconMenu iconButtonElement={iconButtonElement}>
<MenuItem>Add friend</MenuItem>
<MenuItem>Chat</MenuItem>
</IconMenu>}/>
<ListItem
leftAvatar={<CustomAvatar avatarPic={true}/>}
primaryText={"Kory Becker"}
secondaryText={"This is second text"}
secondaryTextLines={1}
rightIconButton={<IconMenu iconButtonElement={iconButtonElement}>
<MenuItem>Add friend</MenuItem>
<MenuItem>Chat</MenuItem>
</IconMenu>}/>
</List>
</MuiThemeProvider>}
}
export default withRouter(MemberList);
CustomAvatar/index.js
import React, {Component} from 'react';
import {withRouter} from 'react-router-dom';
import Avatar from 'material-ui/Avatar';
class CustomAvatar extends Component {
render(){
if(this.props.avatarPic){
return(<Avatar size={40} src={"http://www.example.com/myimage.png"} />)
}else{
return(<Avatar size={40}>A</Avatar>)
}
}
}
export default withRouter(CustomAvatar);
The cause of your problem
Your problem is caused because the material-ui v0 library expects the leftAvatar prop to be an Avatar component, and so relies on internal values of Avatar behind the scenes. Since your CustomAvatar is not directly an Avatar, material-ui does not find these internals and so the styling does not work.
Specifically, if you take a look at the source of ListItem, you'll notice a pushElement function that takes child components like leftAvatar and assigns styling by setting the style prop:
pushElement(children, element, baseStyles, additionalProps) {
if (element) {
const styles = Object.assign({}, baseStyles, element.props.style);
children.push(
React.cloneElement(element, {
key: children.length,
style: styles,
...additionalProps,
})
);
}
}
Your CustomAvatar makes no use of this style prop, so you never receive the necessary CSS styling. That's the cause of your layout issues.
You have a couple of options to fix this depending on whether you are willing to upgrade to v1 or not.
Code that fixes it
class CustomAvatar extends Component {
render() {
const { showPicture, ...other } = this.props;
if (showPicture) {
return (<Avatar size={40} {...other} src={"http://www.example.com/myimage.png"} />);
} else {
return (<Avatar size={40} {...other}>A</Avatar>);
}
}
}
As discussed above, the pushElement function sets the style prop. Right now, you're not using it, so your Avatars get no styling. The {...other} spreads this prop down into your Avatars so that they get the right styling and your layout works.
But, you should upgrade to v1
v1 should have a stable release in the early quarters of 2018, and it fixes a lot of these kinds of problems. Instead of spending time working through these kinds of issues and working with v0, you should upgrade and learn the new (and, imho, improved) way.
I also have faced same problem you could fix this by wrap your custom compoent into PAPER component of material-ui. Please see code below:
import React, {Component} from 'react';
import {withRouter} from 'react-router-dom';
import MuiThemeProvider from 'material-ui/styles/MuiThemeProvider';
import {List, ListItem} from 'material-ui/List';
import IconMenu from 'material-ui/IconMenu';
import MenuItem from 'material-ui/MenuItem';
import CustomAvatar from 'routes/CustomAvatar';
import Paper from 'material-ui/Paper';
class MemberList extends Component {
render(){<MuiThemeProvider>
<List>
<ListItem
leftAvatar={<Paper zDepth={2} circle={true}><CustomAvatar avatarPic={false}/></Paper>}
primaryText={"Mike Tailor"}
secondaryText={"This is first text"}
secondaryTextLines={1}
rightIconButton={<IconMenu iconButtonElement={iconButtonElement}>
<MenuItem>Add friend</MenuItem>
<MenuItem>Chat</MenuItem>
</IconMenu>}/>
<ListItem
leftAvatar={<Paper zDepth={2} circle={true}><CustomAvatar avatarPic={true}/></Paper>}
primaryText={"Kory Becker"}
secondaryText={"This is second text"}
secondaryTextLines={1}
rightIconButton={<IconMenu iconButtonElement={iconButtonElement}>
<MenuItem>Add friend</MenuItem>
<MenuItem>Chat</MenuItem>
</IconMenu>}/>
</List>
</MuiThemeProvider>}
}
export default withRouter(MemberList);

Issue using AsyncTypeahead from react-bootstrap-typeahead package

I'm having an issue using the AsyncTypeahead from the react-bootstrap-typeahead project, where it seems like my onSearch handler is not getting called. I can see the typeahead on the page, but when I type in it, handleSearch is not being executed and I don't see any console logging. Here's a short example:
import React, {PropTypes, Component} from 'react';
import AsyncTypeahead from 'react-bootstrap-typeahead';
class CustomTypeahead extends Component {
state = { results: [] }
handleSearch(event) {
console.log("Show me what you got")
// fetch data here and set state to results in a promise
// this.setState(results)
}
render() {
return (
<div>
<AsyncTypeahead
onSearch={this.handleSearch.bind(this)}
options={this.state.results}/>
</div>
)
}
}
Any suggestions or insights are really appreciated!!!
Fixed by using:
import { AsyncTypeahead } from 'react-bootstrap-typeahead';
instead of
import AsyncTypeahead from 'react-bootstrap-typeahead';
and updating to version ^1.0.0 for react-bootstrap-typeahead

Categories

Resources