Show popup based on state - semantic-ui

I use a Popup to show an error message on an input if validation fails.
<Popup
trigger={InputComponent}
open={state.error}
content={errorMessage}
/>
This works fine, but the annoying part is that when I focus the element an empty popup appears. I can't disable this behaviour for as far as I know.
I've tried adding on={null} and on="none", but all this does not work.
Any ideas? It would be nice to disable triggering the popup, but to allow it to be visible on state value only.

If anyone facing the same issue, the easiest fix would be to add a custom popup style to your popup tag and define opacity with the state as below.
const style = {
opacity: this.state.isOpen ? "1" : "0"
}
<Popup style={style} trigger={<Button icon='add' />} content='Add users to your feed'/>

The usage is very similar to one of the cases mentioned in the docs: https://react.semantic-ui.com/modules/popup#popup-example-controlled
Make sure state.error returns bool type and not string bool and finally, check you are able to close it after the popup opens using onOpen handler as an added measure to make sure you are able to atleast control the component's state.
Finally, as a hack, you can add a {{display: "none"}} through Popup's style prop when this.state.error === true
An example usage from SUI docs of a Popup that automatically after 2.5 seconds:
import React from 'react'
import { Button, Grid, Header, Popup } from 'semantic-ui-react'
const timeoutLength = 2500
class PopupExampleControlled extends React.Component {
state = { isOpen: false }
handleOpen = () => {
this.setState({ isOpen: true })
this.timeout = setTimeout(() => {
this.setState({ isOpen: false })
}, timeoutLength)
}
handleClose = () => {
this.setState({ isOpen: false })
clearTimeout(this.timeout)
}
render() {
return (
<Grid>
<Grid.Column width={8}>
<Popup
trigger={<Button content='Open controlled popup' />}
content={`This message will self-destruct in ${timeoutLength / 1000} seconds!`}
on='click'
open={this.state.isOpen}
onClose={this.handleClose}
onOpen={this.handleOpen}
position='top right'
/>
</Grid.Column>
<Grid.Column width={8}>
<Header>State</Header>
<pre>{JSON.stringify(this.state, null, 2)}</pre>
</Grid.Column>
</Grid>
)
}
}
export default PopupExampleControlled

Related

In React, how can I apply a CSS transition on state change, re-mount, or re-render?

Say I have a React functional component with some simple state:
import React, { useState } from 'react'
import { makeStyles } from "#material-ui/core"
export default function Basket() {
const [itemCount, setItemCount] = useState<number>(0)
return (
<div>
<Count count={itemCount} />
<button onClick={() => setItemCount(itemCount + 1)}>
Add One
</button>
</div>
)
}
function Count({count}: {count: number}) {
const classes = useStyles()
return (
<div className={classes.count}>
{count}
</div>
)
}
const useStyles = makeStyles({
count: {
backgroundColor: "yellow",
transition: "backgroundColor 2s ease" // ???
}
}
I want the Count component to apply a property whenever the count changes and then remove it again; say, turn on backgroundColor: yellow for 2 seconds and then gradually fade it over 1 second. What's the simplest way to achieve this?
Note that presumably this could be either triggered by a state change on the parent or by a re-rendering of the child. Alternatively, I could add the key property to <Count/> to force a re-mount of the child:
<Count
key={itemCount}
count={itemCount}
/>
Any of those would be acceptable; I'm looking for the simplest, cleanest solution, hopefully one that doesn't require additional state and is compatible with Material-UI styling APIs.
Just an idea.
const Component = () => {
useEffect(() => {
// will trigger on component mount
return () => {
// will trigger on component umount
}
}, [])
}
...
document.getElementById('transition').classList.add('example')
You can use useEffect along with useRef containing a reference to the element or directly getting it with document.getElementById and then update the transition class that way in component mount/unmount. Not sure if it'll work, I haven't tested it myself.

Trying to show tooltip in react-big-calendar month view but row covers it up?

I am trying to show a basic tooltip when the user clicks on an event in the calendar with a few event details but the tooltip gets covered by the next row.
Currently, I'm trying to use the slotPropGetter prop to change the style of a cell to include z-index: -1 and inline-styling the tooltip with z-index: 1000 but to no avail.
Here's my current component:
export default() =>{
const eventStyleGetter = ({ color }) => {
const style = {
backgroundColor: color,
opacity: 0.8,
zindex: "6",
position:"relative",
};
return {
style: style,
};
};
const slotStyleGetter = () =>{
const style = {
position: "relative",
zindex:0,
height:"100px",
}
return{
style: style
}
}
const CalendarElement = (
<Calendar
localizer={localizer}
defaultDate={new Date()}
events={events}
style={{ height: 500 }}
eventPropGetter={(event) => eventStyleGetter(event)}
slotPropGetter={(slot) => slotStyleGetter(slot)}
onSelectSlot={(e) => handleSlotSelect(e)}
selectable
popup
components={{
event: EventPopover,
}}
/>
);
return(
{CalendarElement}
)
}
The issue isn't the cell z-index, but that of your tooltip. What are you using to render your tooltip? Under the hood, RBC has react-bootstrap#^0.32.4, which uses react-overlay#^0.8.0. If you use react-overlay to create your tooltips, you can portal them, which should automatically handle your z-index issue.
The way to correctly implement this is to use Reactstrap/Bootstrap Popover (based on Popperjs) rather than plain CSS. It worked in this case.

Change parent component background on hover in reactJS

I have following React code
Code
What I would like is to when I hover my "E-commerce" picture App component background should change on "E-commerce" picture background.
So respectively and for other pictures.
I will be very grateful if you help me solve this problem.
Context, according to the React docs, should be used only for truly global state like current user or theme. Using context for components makes them less reusable.
updated code
Your component tree is App -> SolutionBox -> SolutionItem.
You want to "react" to an event in SolutionItem in App but there is SolutionBox inbetween them so you have to thread the event thru SolutionBox to App.
Step 1
Add a prop to SolutionItem called on OnHover, this will be a function call back that any parent component can use to react to changes.
function SolutionsSectionBoxItem({ solutionIMG, onHover }) {
let callOnHover = state => {
if (_.isFunction(onHover)) {
onHover(state);
}
};
return (
<div className="solutions-section-item-box">
<img
src={solutionIMG}
alt=""
onMouseEnter={() => {
callOnHover(true);
}}
onMouseLeave={() => {
callOnHover(false);
}}
className="solutions-section-item-img"
/>
</div>
);
}
Step 2
Add a prop to SolutionBoxItem called on BGChanged, this will again be a function call back that will be called when any solutionitem onhover happens. This function will take a menuName string and pass either the current menu name or default.
function SolutionsSectionBox({ onBGChanged }) {
let callBGChanged = menuName => {
if (_.isFunction(onBGChanged)) {
onBGChanged(menuName);
}
};
return (
<div className="solutions-section-box-box">
<SolutionItem
solutionIMG={Ecommerce}
onHover={state => {
callBGChanged(state === true ? "Ecommerce" : "default");
}}
/>
<SolutionItem
solutionIMG={SalesMarketing}
onHover={state => {
callBGChanged(state === true ? "SalesMarketing" : "default");
}}
/>
<SolutionItem
solutionIMG={Analytics}
onHover={state => {
callBGChanged(state === true ? "Analytics" : "default");
}}
/>
<SolutionItem
solutionIMG={Middleware}
onHover={state => {
callBGChanged(state === true ? "Middleware" : "default");
}}
/>
</div>
);
}
Step 3
In the App component listen for the changes. In here we now set state when ever the mouse enters or leaves a solution item. From here you have to change the background, you are using css to control the background url, this will be harder since you now need css class for each background type. You could use the bgImage state value to change the name of the extra css className like 'AppSalesMarketing', 'AppEcommerce', etc.
export default function App() {
const [bgImage, setbgImage] = useState(E);
const onBGChanged = menuName => {
setbgImage(menuName);
};
return (
<div className={`App ${bgImage === "default" ? "" : `App${bgImage}`}`}>
<SolutionBox onBGChanged={onBGChanged} />
</div>
);
}
In CSS
Leave the original App class but based on the bgImage value add an additional one using the name of the bgImage + App like below to cascade down the updated background-image value.
.AppEcommerce {
background-image: url(https://placekitten.com/600/600);
}
.AppSalesMarketing {
background-image: url(https://placekitten.com/500/800);
}
.AppAnalytics {
background-image: url(https://placekitten.com/800/500);
}
.AppMiddleware {
background-image: url(https://placekitten.com/700/700);
}
Extra
I added lodash to test that the incoming props are functions before I call them, it is good to do defensive programming because you never know who may use your component in the future.
let callBGChanged = menuName => {
if (_.isFunction(onBGChanged)) {
onBGChanged(menuName);
}
};
Two ways to solve the problem. One is passing down a function to update state, the other is to useContext. In this case it makes sense to use context because you are passing down a function through multiple components that do not care about the function.
First thing to do is make the background image dynamic in the div's style and use context:
// Put this outside the component
export const BackgroundContext = React.createContext(null);
// -- snip
const [backgroundImage, setBackgroundImage] = useState(Ecommerce);
const updateBackgroundImage = newImage => setBackgroundImage(newImage);
// -- snip
<BackgroundContext.Provider value={updateBackgroundImage}>
<div className="App" style={{ backgroundImage: `url(${backgroundImage})` }}>
{/* -- snip */}
</BackgroundContext.Provider>
Now in your SolutionsSectionBoxItem component you can import the background context:
import BackgroundContext from "../App";
Then using that context and react's mouseover api, update the selected background image:
const setBackgroundImage = useContext(BackgroundContext);
// -- snip
<img onMouseOver={() => setBackgroundImage(solutionIMG)} {/* -- snip -- */} />
You can read more here: https://reactjs.org/docs/hooks-faq.html#how-to-avoid-passing-callbacks-down

collectionRferece.doc() required its first argument to be of not empty string but was undefined?

my title question maybe duplicated but, I think its different :D..
I want to update array in firestore ... first time it work ,the second time will
give me this
warning: virtualizedlist: missing keys for items
and give me this error: collectionRferece.doc() required its first argument to be of not empty string but was undefined
I explained my code step by step.
Home.js
I receive data from firestore and i send it as a prop to projectList
render(){
const {projects,auth}=this.props;
return(
<ProjectList projects={projects} />
)
}
}
const mapStateToProps=(state)=>{
return{
projects:state.firestore.ordered.projects,
}
}
export default compose(connect(mapStateToProps),firestoreConnect([{collection:'projects',orderBy:['createdAt','desc']}])) (Feed);
ProjectList.js
there is just a flat list and sending data as a prop to projectsummery
const ProjectList =({projects})=> {
return(
<FlatList
data={projects}
refreshing={true}
renderItem={(project)=>{
return(
<ProjectSummery project={project} key={project.item.id}
keyExtractor={(item, index) => item.item.id}
/>
)
} }
/>
)
}
ProjectSummery.Js
there is some design and button, when i press the button i send project.item.id which is a (doc id) in firestore to project actions.js there is likePosts function which is update the array in firestore
import {likePosts} from '../Store/Actions/ProjectActions'
const ProjectSummery =(props)=> {
const {project,auth}=props
console.log(project.item.id);
return(
<>
<Container style={{flex:0,height:180}} >
<Content >
<Card>
<CardItem>
<Left>
<Button transparent onPress={()=>props.likePosts(project.item.id)}>
<Text>{project.item.likes.length} Likes</Text>
</Button>
</Left>
</Card>
</Content>
</Container>
</>
)
}
const mapDispatchToProps=(dispatch)=>{
return{
likePosts:(postId)=>dispatch(likePosts(postId))
}
}
export default connect(null,mapDispatchToProps)(ProjectSummery);
Actions.js
here i have likePosts fuction which update the array in firestore,
the fist time it works but the second time (project.item.id) postId will be undefined and i have no idea what's going on :D
export const likePosts =(postId)=>{
return (dispatch,getState,{getFirebase,getFirestore})=>{
const profile=getState().firebase.profile
const authId=getState().firebase.auth.uid
const firestore=getFirestore()
console.log(postId);
firestore.collection('projects').doc(postId).update({
likes:firestore.FieldValue.arrayUnion({
likedAt:new Date(),
likedBy: authId,
name: profile.firstName + profile.lastName
})
})
}}
Just in case anyone ends up on this question facing a similar issue, there is this SO question that appears to be different but the issue is caused by the same reason.
Check my answer there and the comments in under the question itself.
In addition to that, you can also check the Pagination section of this article for more guidance.
Original Answer:
What's happening is that when you click a like button the first time, it's working as expected so it gets the proper postId and then continues with the process you have defined. However, when you try the 2nd time it fails to fetch the postId as it's already liked.
The idea is that you'll need to either define an if statement and specify what should happen if it's already clicked and it get's clicked again (possibly storing the postId somewhere the first time and using it from there), or make an initial check that returns a specific message to the user if it's already clicked.
The issue has nothing to do with Firestore itself but with the button and the states of liked/unliked.
Here is one nice interactive example on codepen.io of a proper way of building like buttons using react. React Like Button
HTML
<div id="example"></div>
CSS
.btn-primary {
background-color: #23aa4e;
border-color: #177d37;
}
#example {
margin: 3rem;
}
.customContainer {
border: 1px solid black;
}
JS
class LikeButton extends React.Component {
constructor() {
super();
this.state = {
liked: false
};
this.handleClick = this.handleClick.bind(this);
}
handleClick() {
this.setState({
liked: !this.state.liked
});
}
render() {
const text = this.state.liked ? 'liked' : 'haven\'t liked';
const label = this.state.liked ? 'Unlike' : 'Like'
return (
<div className="customContainer">
<button className="btn btn-primary" onClick={this.handleClick}>
{label}</button>
<p>
you {text} this. Click to toggle.
</p>
</div>
);
}
}
ReactDOM.render(
<LikeButton />,
document.getElementById('example')
)

Selected ListItem value using SelectableContainerEnhance in Material-UI

I'm really new to ReactJS and trying to work with Material-UI components on a new Meteor app I'm working with. A classic use case has come to my needs: a list of items changes the UI when the user selects or not some ListItem. Surprisingly, I found that React isn't easy with parent-child component relations like that.
I tried to follow the Material-UI Docs, implementing SelectableList component like the docs suggests using the SelectableContainerEnhance class. Then I went this way:
const {ListItem, Avatar, Divider} = mui;
App = React.createClass({
mixins: [ReactMeteorData],
getMeteorData() {
return {
players: Players.find({}, { sort: { score: -1 } }).fetch()
}
},
render() {
return (
<SelectableList subheader="Players list">
{this.data.players.map((player) => {
return (
React.Children.toArray([
<Divider />,
<ListItem
value={player._id}
primaryText={player.name}
secondaryText={player.score}
leftAvatar={<Avatar>{player.name}</Avatar>} />
])
);
})}
</SelectableList>
<Divider />
{ true /* What to do now? */ ?
(<span>Thanks!</span>) :
(<span>Click a player to select</span>)}
);
}
});
Ok, the list items has become selectable. But how to know if any ListItem is selected? And how to get the value and adjust the UI according to it?
They talk about setting up a valueLink in the documentation.
<SelectableList
subheader="Players List"
valueLink={{
value: this.state.selectedIndex,
requestChange: this.handleUpdateSelectedIndex
}}>
And then define a handleUpdateSelectedIndex to set the state:
getInitialState() {
return {selectedIndex: 1};
},
handleUpdateSelectedIndex(e, index) {
this.setState({
selectedIndex: index,
});
},
This will give you this.state.selectedIndex on your App component that you can do whatever you need to do with it.

Resources