How do I update state to specific value? - reactjs

I hope you're all well.
I would be so grateful if any of you can shed some light on the following question..
There are two relevant components:
Parent component which fetches data using GraphQL
let authors = "";
const { loading, data } = useQuery(FETCH_AUTHORS_QUERY);
console.log(`Loading: ${loading}`);
//console.log(data);
if (data) {
authors = { data: data.getAuthors };
}
return (
<Grid columns={3}>
<Grid.Row className="page-title">
<h1>Recent Essays</h1>
</Grid.Row>
{loading ? (
<h1>Loading essays..</h1>
) : (
authors.data &&
authors.data.map(author => (
<Grid.Column key={author.id} style={{ marginBottom: 20 }}>
<AuthorCard author={author} />
</Grid.Column>
))
)}
<Grid.Row></Grid.Row>
</Grid>
);
}
const FETCH_AUTHORS_QUERY = gql`
{
getAuthors {
id
Author
Description_1
Description_2
}
}
`
Child component called 'AuthorCard' (you can see placed in the parent component above):
function AuthorCard({ author: { Author, Description_1, id } }) {
const [writer, setWriter] = useState();
return (
<Card fluid>
<Card.Content>
<Image
floated="right"
size="mini"
src="https://image.cnbcfm.com/api/v1/image/105691887-1556634687926ray.jpg?v=1576249072"
/>
<Card.Header>{Author}</Card.Header>
<Card.Meta as={Link} to={`/essays`}></Card.Meta>
<Card.Description>{Description_1}</Card.Description>
</Card.Content>
<Card.Content extra>
<Button as="div" labelPosition="right">
<Button color="teal" basic>
<Icon name="heart" />
</Button>
<Label basic color="teal" pointing="left"></Label>
</Button>
<Button labelPosition="right" as={Link} to={`/essays`}>
<Button
color="blue"
basic
key={Author.id}
onClick={() => setWriter(Author)}
>
<Icon name="comments" />
</Button>
</Button>
</Card.Content>
</Card>
);
}
export default AuthorCard;
The issue is as follows:
When I click the as={Link} to={/essays} button in the child component, I would like to 'setWriter' state to the individual Author whose button I am clicking on.
However, I am not able to specify the individual author whose button is being clicked on. React thinks I want to 'setWriter' for the entire list of authors. For example, when I console.log(Author) within the button, I can see every author in the list printed in the console.
I have tried adding id, using event.target .value, and onChange instead of onClick.
I just haven't been able to target the individual author in order to update the state (which will be needed in a different component).
Please let me know if anything isn't clear or if it would be helpful to provide more details.
Thanks in advance and happy coding!!!
Dan

You should use a destructuring assignment expression that makes it possible to unpack values from arrays, or properties from objects, into distinct variables.
I think you should declare your child component like this:
function AuthorCard({ Author, Description_1, id }) {
...
...
...
}

Thanks for the reply.
I finally solved the issue. I had to use:
onClick={e => setWriter(e.currentTarget.Author)}
I hadn't known about using 'current target.'
I can then transfer the state to other components through useContext.
Hopefully this can help someone else!
Dan

Related

React State Update Working for First State Update but Failing on any further updates

So I have a react component that calls a function to at an interval to get balances for an account. The first load of the component successfully updates from loading and not ready to ready and pulls in the users account balances.
Upon completing a transaction and on the next account balances poll, the account balances are updated in the state and log with the correct value however not updated on the components UI (or any child components). I have also tried pulling out one of the variables, which can be seen below as pcDaiValue. This too updates on first load and first fetch of account balances, however does not update any further such as after a transaction. The state does update and logs however the UI does not update to reflect this state.
I initially believed this was a child component problem in a previous post however now I realise it is the state of the parent component not being updated.
If you look at the pcDaiValue state - it will be updated in the state but not the UI. Wondering a possible solution to this.
Any help would be appreciated here
Code below with irrelevant parts removed:
function ProtektDepositCard({
children,
item,
lendingMarketMetrics,
tokenPrices,
}: Props): React.Node {
const [accountBalances, setAccountBalances] = useState({ready:false})
const [pcDaiValue, setPCDaiValue] = useState(0)
useInterval(async () => {
(async function(){
const newAccountBalances = await GetAccountBalances(
web3Context.address,
tokenPrices,
contracts,
[item.underlyingTokenSymbol, item.pTokenSymbol, item.reserveTokenSymbol, item.shieldTokenSymbol, item.coreTokenSymbol],
[item.underlyingTokenDecimals, item.pTokenDecimals, item.reserveTokenDecimals, item.shieldTokenDecimals, item.coreTokenDecimals],
[item.pTokenAddress, item.pTokenAddress, item.shieldTokenAddress, item.shieldTokenAddress, item.pTokenAddress],
[null, item.underlyingTokenSymbol, null, item.reserveTokenSymbol, null]
)
console.log('retrieved account balances')
console.log(newAccountBalances)
console.log('retrieved pcdai')
console.log(pcDaiValue)
if(newAccountBalances['pcdai']){
const pTokenValue = newAccountBalances['pcdai']['token']
setPCDaiValue(pTokenValue)
}
setAccountBalances({...newAccountBalances})
})();
}, 5000)
console.log('logging account balances')
console.log(accountBalances)
return ( (coverage.loading) ? <Card><Card.Body><Dimmer active loader /></Card.Body></Card> :
<AccordionItem
key={accountBalances}
>
<div>PCDAI VALUE: {String(pcDaiValue)}</div>
<Card className="mb-1">
<AccordionItemHeading>
<AccordionItemButton>
<Card.Body>
<Grid.Row alignItems="center" justifyContent="center">
<Grid.Col width={2}>
<Avatar
imageURL={`assets/${item.coreTokenLogo}.png`}
style={{"verticalAlign":"middle"}}
/>
<Text size="h4" align="center" RootComponent="span" className="ml-2">{item.coreToken.toUpperCase()}</Text>
</Grid.Col>
<Grid.Col width={3}>
<Avatar
imageURL={`assets/${item.protocolLogo}.png`}
style={{"verticalAlign":"middle"}}
size="lg"
/>
<Text size="h4" align="center" RootComponent="span" className="ml-1">{item.underlyingProtocol.toUpperCase()}</Text>
</Grid.Col>
<Grid.Col width={2}>
<Text size="h4" align="center" className="mb-0">{`${numeral(coverage.netAdjustedAPR).format('0.00')}%`}</Text>
</Grid.Col>
<Grid.Col width={2}>
<Text align="center">
{`${numeral(coverage.pTokenTotalDepositUsd).format('$0,0a')}`}
</Text>
<Text align="center" size="sm" muted>
{`${numeral(parseFloat(ethers.utils.formatUnits(coverage.pTokenTotalDepositTokens,item.underlyingTokenDecimals))).format('0,0a')} ${item.underlyingTokenSymbol.toUpperCase()}`}
</Text>
</Grid.Col>
<Grid.Col width={3} className="text-center">
<Tag.List>
<Tag rounded color="purple">{item.riskTag}</Tag>
</Tag.List>
</Grid.Col>
</Grid.Row>
</Card.Body>
</AccordionItemButton>
</AccordionItemHeading>
<AccordionItemPanel>
<ProtektHoldingSection
item={item}
tokenPrices={tokenPrices}
web3Context={web3Context}
gasPrice={gasPrice}
contracts={contracts}
coverage={coverage}
claimsManager={claimsManager}
accountBalances={accountBalances}
onRequery={()=>{
console.log('forcing update')
setRequery(prevState=>prevState + 1)
}}
actionCount={requery}
key={accountBalances}
/>
<Card.Body>
<Grid.Row>
<Grid.Col width={6}>
<h5 className="m-0 text-muted">{`COST`}</h5>
<p>{`${numeral(coverage.coverageFeeAPR).format('0.00')}% for ${coverage.coverageRatioDisplay} coverage`}</p>
<h5 className="m-0 text-muted">{`BACKED BY`}</h5>
<p>{`${item.backedByDisplay}`}</p>
</Grid.Col>
<Grid.Col width={6}>
<h5 className="m-0 text-muted">{`CLAIMS`}</h5>
<p>{`${item.claimsManagerDisplay}`}</p>
</Grid.Col>
</Grid.Row>
<Grid.Row>
<Grid.Col width={12}>
<h5 className="m-0 text-muted">{`COVERAGE FOR`}</h5>
<p>{`${item.coverageDisplay}`}</p>
</Grid.Col>
</Grid.Row>
</Card.Body>
{ !web3Context.ready ?
(<Card.Body><Text className="text-center font-italic">Connect Wallet Above<span role="img">👆</span></Text></Card.Body>) :
!accountBalances.ready ? <Card.Body><Dimmer active loader /></Card.Body> :
accountBalances[item.pTokenSymbol]["token"] === "0" ?
renderDepositCard() :
(<div></div>)
}
</AccordionItemPanel>
</Card>
</AccordionItem>
)
}
/** #component */
export default ProtektDepositCard;
Many thanks for any help / feedback.
Update
Thank you for the comments so far - I have tried just incrementing a basic counter in the useInterval function and still the UI is not updated with relevant state:
function ProtektDepositCard({
children,
item,
lendingMarketMetrics,
tokenPrices,
}: Props): React.Node {
const [counter, setCounter] = useState(0)
useInterval(async () => {
(async function(){
/* other logic */
setCounter(counter+1)
})();
}, 5000)
console.log(`logging counter: ${counter}`)
return (
/* Other logic */
<div>counter: ${counter}</div>
/* Other logic */
)
/** #component */
export default ProtektDepositCard;
Minimised example but still seem to be having state errors, the state logs correctly with the update counter value every 5 seconds however it is not reflected in the UI.
Unfortunately, there appears to be a bug in the library react-accessible-accordion that you're using:
https://github.com/springload/react-accessible-accordion/issues/305
If you put the counter outside of the accordion, you will see it update happily. Or if you toggle the accordion, it will also update!

Extending React Component in order to add Custom Fields?

i'm trying to create a component that returns a Flex Element from chakra-ui , naturally i would like to keep the Auto Complete from the Flex Type when using my component but additionally i would like to add an extra prop to it called "iconName".
This is the code i wrote:
type TFlex = typeof Flex
const Tab: TFlex = ({ iconName, ...props }) => {
return (
<Flex align="center" height="12" minWidth="12" {...props}>
<Icon name={iconName} size="8" />
</Flex>
)
}
export default Tab
But doing so is yielding the following error:
From my understanding I would need to:
Extend the FlexProps Inteface and call it MyFlexProps
Extend the Flex Type and call it MyFlexType
Type my Tab instance with MyFlexType
But unfortunately i could not make this work and also i'm not sure if that's the right approach?
Thank you very much for your help.
Turns out this solved my issue:
interface ITab extends FlexProps {
iconName?: string
}
const Tab: React.FC<ITab> = ({ iconName, ...props }) => {
return (
<Flex align="center" height="12" minWidth="12" {...props}>
<Icon name={iconName} size="8" />
</Flex>
)
}

How to pass data to React components with redux?

I am completely new to the field which is why I might be having a silly question over here.
I have set up a react-redux app. All the redux boilerplate is ready and is functioning properly. Now, I am willing to add a feature that will take data from one React Component and pass it to another, while hiding it or deleting it from the first component.
My question is, should I set up new action and update my root reducer in order to do that or there is an easier way to do that?
This is my first component which I am willing to take data from by clicking the "Rent" button. Also, I want the data piece to temporarily disappear from this component and appear in the second one.
class Available extends Component {
componentDidMount() {
this.props.getRentals();
}
onDeleteClick = (id) => {
this.props.deleteRental(id);
}
render() {
const { rentals } = this.props.rental
return (
<Container fluid>
<h2>List of Available Bikes</h2>
<ListGroup>
<TransitionGroup className="rental-list">
{ rentals.map(({ _id, name, bikeType, price }) => (
<CSSTransition key={_id} timeout={500} classNames="fade">
<ListGroupItem>
{name} / {bikeType} / ${price} / per hour
<Button
className="remove-btn float-right"
color="danger"
size="sm"
onClick={this.onDeleteClick.bind(this, _id)}
>Delete</Button>
<Button
className="remove-btn float-right"
color="primary"
size="sm"
onClick={this.onRentClick(this, _id)}
>Rent</Button>
</ListGroupItem>
</CSSTransition>
))}
</TransitionGroup>
</ListGroup>
</Container>
)
}
}
And this is the second component that I am willing to pass the data to. Also, I am willing to add a feature that will bring the data piece back to the first component when I click the "Cancel Rent" button.
class Rented extends Component {
componentDidMount() {
this.props.getRentals();
}
render() {
const { rentals } = this.props.rental
return (
<Container fluid>
<h2>List of Rented Bikes</h2>
<ListGroup>
<TransitionGroup className="rental-list">
{ rentals.map(({ _id, name, bikeType, price }) => (
<CSSTransition key={_id} timeout={500} classNames="fade">
<ListGroupItem>
{name} / {bikeType} / ${price} / per hour
<Button
className="remove-btn float-right"
color="danger"
size="sm"
>Cancel Rent</Button>
</ListGroupItem>
</CSSTransition>
))}
</TransitionGroup>
</ListGroup>
</Container>
)
}
}
Please let me know what is the best solution here :)

antd SubMenu "TypeError: Cannot read property 'isRootMenu' of undefined"

I use antd 3.15 and GraphQL to fetch data and generate a list of SubMenu and Menu.Item inside of Menu. However, I got the error message like this Uncaught TypeError: Cannot read property 'isRootMenu' of undefined I have no idea what is wrong with my code. isRootMenu is not a prop listed anywhere on the doc. ant.design/components/menu/#header and when I hardcoded all the SubMenu and Menu.List there is no problem. Can I iterate data from GraphQL to generate the SubMenu and Menu.List?
Can someone help me with this issue, please? Thank you! Here is my code:
import * as React from 'react';
import './SideNav.scss';
import { Menu, Icon } from 'antd';
import gql from 'graphql-tag';
import { Query } from 'react-apollo';
const FLOORS_QUERY = gql`
query {
getAllFloors {
id
floorName
rooms {
id
roomName
roomNumber
roomDescription
}
}
}
`;
export default class SideNav extends React.Component {
render() {
return (
<Menu theme="light" defaultSelectedKeys={['1']} mode="inline">
<Query query={FLOORS_QUERY}>
{({ loading, error, data }) => {
if (loading) return <h4> loading... </h4>;
if (error) console.log(error);
console.log(data);
return (
<React.Fragment>
{data.getAllFloors.map((floor) => (
<SubMenu
key={floor.id}
title={
<span>
<Icon type="plus" />
<span>{floor.floorName}</span>
</span>
}
>
<React.Fragment>
{floor.rooms.map((room) => (
<Menu.Item key={room.id}>{room.roomNumber}</Menu.Item>
))}
</React.Fragment>
</SubMenu>
))}
</React.Fragment>
);
}}
</Query>
</Menu>
);
}
}
You should pass the props to the submenu.
const CustomComponent = (props) => (
<Menu.SubMenu title='SubMenu' {...props}>
<Menu.Item>SubMenuItem</Menu.Item>
</Menu.SubMenu>
)
so a solution to your question would be to do the following;
move the query outside of the containing menu
pass the props to the SubMenu
const FloorMapSubMenu = ({ id, floorName, rooms, ...other }) => {
return (
<Menu.SubMenu
key={id}
title={
<span>
<Icon type="plus" />
<span>{floorName}</span>
</span>
}
{...other} // notice the other props, this is were the 'isRootMenu' is injected from the <Menu> children
>
<React.Fragment>
{rooms.map((room) => (
<Menu.Item key={room.id}>{room.roomNumber}</Menu.Item>
))}
</React.Fragment>
</Menu.SubMenu>
)
}
class SideNav extends React.Component {
render() {
return (
<Query query={FLOORS_QUERY}>
{({ loading, error, data }) => {
if (loading) return <h4> loading... </h4>
if (error) console.log(error)
console.log(data)
return (
<Menu theme='light' defaultSelectedKeys={['1']} mode='inline'>
{data.getAllFloors.map((floor, i) => (
<FloorMapSubMenu key={i} id={floor.id} floorName={floor.floorName} rooms={floor.rooms} />
))}
</Menu>
)
}}
</Query>
)
}
}
I found out the Ant design SubMenu needs to use the parent to check some properties like isRootMenu at
SubMenu.js:260
getPopupContainer = props.parentMenu.isRootMenu ? props.parentMenu.props.getPopupContainer : function (triggerNode) {
return triggerNode.parentNode;
}
In order to solve it you need to manually pass parent props into SubMenu like
<Menu.SubMenu {...this.props}/>
to solve the problem. Hope this helps u
Related Github issue item https://github.com/react-component/menu/issues/255
I had this issue while trying to add a <div> as a child of Menu. I just added an empty Menu.Item as the first child of my menu, and the error went away.
<Menu>
<Menu.Item style={{display: 'none'}} />
<div>...</div>
</Menu>
I ran into the same issue. It seems Antd does not allow to place arbitrary components into a Menu/SubMenu. My guess is that Menu.Item needs to get some props, which are passed from Menu/SubMenu to its children.
So you can either create a custom component that passes all props down, or remove the inner <React.Fragment> declaration (the one that is inside the SubMenu), which is not needed anyway.
I was able to make it work by putting the <Query> Component at the top:
<Query query={FLOORS_QUERY}>
{({ loading, error, data }) => {
if (loading) return <Spin />;
if (error) console.log(error);
console.log(data);
return (
<Menu theme="light" defaultSelectedKeys={['1']} mode="inline">
{data.getAllFloors.map((floor) => (
<SubMenu
key={floor.id}
title={
<Link to="/{floor.id}">
<span>
<Icon type="plus" />
<span>{floor.floorName}</span>
</span>
</Link>
}
>
{floor.rooms.map((room) => (
<Menu.Item key={room.id} onClick={this.showRoomProfile}>
{room.roomNumber}
</Menu.Item>
))}
</SubMenu>
))}
</Menu>
);
}}
</Query>
According to the Typescript definitions the childrens of Menu should be of kind Item, SubMenu, ItemGroup or Divider. If you must place a different component on the Header, wrap the Menu and the desired component on a Header component component as such:
import { Layout } from 'antd';
const { Header, Footer, Sider, Content } = Layout;
<Layout>
<Layout.Header>
<div className="logo" />
<Menu>
<Menu.Item key="1">nav 1</Menu.Item>
<Menu.Item key="2">nav 2</Menu.Item>
</Menu>
<Layout.Header>
</Layout>
I have run into the same issue. But my issues was I have using ternary condition to show some menu's dynamically inside part used the <></> element. That caused me this issue. Once removed that everything work fine.

Flickering issue with antd

I am using the Ant Design components for some interfaces in a React App but I'm noticing an issue with Modals and Tooltips. Usually when the modal is empty or is simple - e.g has only a text it is going to show properly but in case there are more elements on it (on production scenario) it flickers.
You can visually watch the flickering issue here: https://drive.google.com/file/d/1ODsj-aGz6saHJLXLI7sJaesgHKKPrvD2/view
Hope to get some answers :-) Thank you!
The visibleRequest state variable is triggered true by a button in the interface.
EDIT: As requested here's my code:
renderRequests=()=>{
const data = [
{name:"Request #1",description:"Request description",status:"0"},
{name:"Request #1",description:"Request description",status:"1"},
{name:"Request #1",description:"Request description",status:"2"}
];
const handleOk = ()=>{
console.log("Submitted");
this.setState({visibleRequest:false})
}
const handleCancel = ()=>{
this.setState({visibleRequest:false})
}
return(
<div style={{overflowY:"scroll"}}>
<Modal
title="Submit new request"
visible={this.state.visibleRequest}
onOk={handleOk}
onCancel={handleCancel}
>
<Input placeholder="Title"/>
<Input.TextArea placeholder="Description"/>
<DatePicker/>
</Modal>
<List
dataSource={data}
renderItem={item=>{
return(
<List.Item>
<List.Item.Meta
title={item.name}
description={item.description}
/>
{item.status==1?<Tooltip title="Request approved">
<Icon type="check-circle" theme="twoTone" twoToneColor="#52c41a" />
</Tooltip>:
item.status==2?<Icon type="close-circle" theme="twoTone" twoToneColor="#7F1C43" />:<Icon type="ellipsis" theme="outlined" />}
</List.Item>
)
}}/>
<Pagination total={20}/>
</div>
)
}

Resources