Selected ListItem value using SelectableContainerEnhance in Material-UI - meteor

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.

Related

Change width of event in FullCalendar (React JS)

I like to change the width of an event in context of React JS.
Similiar questions described here:
How to edit width of event in FullCalendar?
Change Fullcalendar width
...
Unfortunately, in the quoted questions is nothing mentioned how to solve this in a react environment.
I figured it out how to do it. eventRender does no longer exist (v4) but instead different "event render hooks" (v5):
eventClassNames: Specifically for changing the .css of an event
eventContent: To inject content into the event
and others (see:https://fullcalendar.io/docs/event-render-hooks)
Now, depending what you want to achieve, there are two ways to do this in React JS. (Note: I used TypeScript)
Applying CSS change to all events
We can use styled to create our own .css definition for any event and use that as a wrapper (StyleWrapper)
import React from 'react';
import FullCalendar from '#fullcalendar/react';
import timeGridPlugin from '#fullcalendar/timegrid';
import styled from '#emotion/styled';
export interface ISampleProps {}
//our Wrapper that will go around FullCalendar
export const StyleWrapper = styled.div`
.fc-event {
width: 98px !important;
}
`;
//Reacct Functional Component
const Sample: React.FunctionComponent<ISampleProps> = (props) => {
const events = [
/*some events */
];
return (
<>
<div>
<StyleWrapper>
<FullCalendar
plugins={[timeGridPlugin]}
initialView="timeGridWeek"
events={events}
/>
</StyleWrapper>
</div>
</>
);
};
export default Sample;
Apply specific CSS to specific events
With this way, you can tell FullCalendar exactly how an event has to look like depending self-defined props you add to an event. Your self-defined props will be added to extendedProps which will be used in our event render hook eventClassNames
//same imports from earlier (but you don't need "styled" for this one)
const Sample: React.FunctionComponent<ISampleProps> = (props) => {
function eventAddStyle(arg: any) {
//all self-created props are under "extendedProps"
if (arg.event.extendedProps.demanding) {
return ['maxLevel']; //maxLevel and lowLevel are two CSS classes defined in a .css file
} else {
return ['lowLevel'];
}
}
const events = [
{
id: 'a',
title: 'This is just an example',
start: '2022-03-19T12:30:00',
end: '2022-03-19T16:30:00',
backgroundColor: '#74AAEB',
demanding: true //our self-created props
},
{
id: 'b',
title: 'This is another example',
start: '2022-03-17T08:00:00',
end: '2022-03-17T11:30:00',
demanding: false // our self-created props
},
];
return (
<>
<div>
<FullCalendar
plugins={[timeGridPlugin]}
initialView="timeGridWeek"
eventClassNames={eventAddStyle}
events={events}
/>
</div>
</>
);
};
export default Sample;

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.

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

Wordpress Gutenberg autocomplete - saving attributes

I want to develop a custom block that will let the user pick some information from autocomplete. I manage to create the autocomplete component on edit function.
A user can select an item but i don't know how to handle the attribute save.
I'm trying to save the selected item as attribute package_name. I created the onChange function on Autocomplete component but event.target.value is undefined.
Here is my code from block.js
const { __ } = wp.i18n; // Import __() from wp.i18n
const { AlignmentToolbar,
BlockControls,
registerBlockType } = wp.blocks;
const { RichText } = wp.editor;
const { Autocomplete, } =wp.components;
const MyAutocomplete = () => {
const autocompleters = [
{
name: 'fruit',
triggerPrefix: '#',
options: [
{ name: 'Apple', id: 1 },
{ name: 'Orange', id: 2 },
{ name: 'Grapes', id: 3 },
{ name: 'test', id: 4 },
],
getOptionLabel: option => (
<span>
{ option.name }
</span>
),
getOptionKeywords: option => [ option.name ],
isOptionDisabled: option => option.name === 'Grapes',
getOptionCompletion: option => (
<abbr title={ option.name }>{ option.name }</abbr>
),
}
];
function onChangeAuto(newContent){
console.log('autocompletexx '+newContent);
}
function onSelectAuto(event){
console.log(event.target);
console.log( event.target.value);
}
return (
<div>
<Autocomplete completers={ autocompleters }>
{ ( { isExpanded, listBoxId, activeId } ) => (
<div
contentEditable
suppressContentEditableWarning
aria-autocomplete="list"
aria-expanded={ isExpanded }
aria-owns={ listBoxId }
aria-activedescendant={ activeId }
onChange={onChangeAuto }
onSelect={onSelectAuto}
>
</div>
) }
</Autocomplete>
<p class="autocomplete_p">Type # for triggering the autocomplete.</p>
</div>
);
};
registerBlockType( 'residence-gutenberg-block/membership-package-settings', {
title: __( 'Residence - Membership Package Settings' ), // Block title.
icon: 'shield',
category: 'common',
keywords: [
__( 'membership-package-settings' ),
],
attributes:{
package_id:{
type:'string',
select:'p'
},
package_name:{
type:'string',
},
},
edit: function( props ) {
const { attributes: {package_name}, className,setAttributes,isSelected } = props;
return (
<div className={ props.className }>
<form>
<label className="wpresidence_editor_label">Current Package: {package_name}</label>
<MyAutocomplete></MyAutocomplete>
</form>
</div>
);
},
save: function( props ) {
// Rendering in PHP
return null;
},
} );
Passing down onChange, onSelect to the div element won't work, because these attributes are only applicable to the form field elements (as input, select, etc.).
I checked the documentation and the source code and didn't find any details or official approaches for dealing with the case.
However, I'm seeing two possible approaches for getting the selected value:
1. Using Autocomplete onReplace prop
Looking into the Autocomplete's source code, I noticed that onSelect callback invokes onReplace prop with the selected option as array. It may not fit all the cases, but you can give it a try! It may be enough for your case! You can try to add your handler to the onReplace as follows:
<Autocomplete
onReplace={ option => { console.log(option) } }
completers={ autocompleters }>
{ ( { isExpanded, listBoxId, activeId } ) => (
<div
contentEditable
suppressContentEditableWarning
aria-autocomplete="list"
aria-expanded={ isExpanded }
aria-owns={ listBoxId }
aria-activedescendant={ activeId }
/>
) }
</Autocomplete>
2. Listen for <div /> changes manually
You can add onInput, onBlur listeners to the <div />, having an uncontrolled react div component and when the div's value is changed then we can keep the changed value in your parent component's state.
Here's a great discussion, which describes these technique: React.js: onChange event for contentEditable
The good think is that there's already a plugin (based on this discussion) that can do it for you: react-contenteditable.
Firstly you have to convert your <MyAutocomplete /> component to be a statefull (not functional) and then:
import ContentEditable from 'react-contenteditable'
// Boilerplate ...
<Autocomplete completers={ autocompleters }>
{ ( { isExpanded, listBoxId, activeId } ) => (
<ContentEditable
html={this.state.html}
onChange={this.handleChange}
contentEditable
suppressContentEditableWarning
aria-autocomplete="list"
aria-expanded={ isExpanded }
aria-owns={ listBoxId }
aria-activedescendant={ activeId }
/>
) }
</Autocomplete>
Conclusion
I'm surprised that in the Autocomplete's documentation there aren't any details for this case. I guess it's because of the following statement (27.08.2018):
Gutenberg is being developed on GitHub, and you can try an early beta
version today from the plugin repository. Though keep in mind it’s not
fully functional, feature complete, or production ready.
However, one of both mentioned approaches above will help you, until they provide a complete API to work with their components.
I would suggest you to keep wrapping the Wordpress's <Autocomplete /> with your own component - in order to easily refactor your code later, when they release the complete API.
If you have questions, feel free to comment below.

Show popup based on state

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

Resources