Gatsby and Theme-UI ColorModeProvider - reactjs

I was experimenting with various font loading strategies with Gatsby and ended up coping over the source of gatsby-plugin-theme-ui so I can update theme.fonts values once all the fonts are loaded by the client (by default theme.fonts are set to system fonts).
After doing all of that I installed ColorModeProvider and added it to the wrap-root-element.js inside of the ThemeProvider, but it's not completely working. Only text & background colors are updating, but primary, secondary and muted colors are not changing (default colors are used). I can confirm that CSS variables are getting updated each time I change the theme.
Please let me know what I'm missing here?
Demo (Code block colors are not supposed to be changed, because they are handled by another provider)
The actual PR
ThemeProvider:
/** #jsx jsx */
import { jsx, ThemeProvider } from 'theme-ui'
import { useState, useEffect, useCallback, Fragment } from 'react'
import theme from '../gatsby-plugin-theme-ui'
import components from '../gatsby-plugin-theme-ui/components'
import useEventListener from '../hooks/use-event-listener'
const themeUI = { ...theme }
const { fonts: { safe: safeFonts } } = { ...theme }
const safeFontsTheme = {
...Object.assign(
{},
theme,
{ fonts: safeFonts }
)
}
const ThemeUIProvider = ({ element }) => {
const [theme, setTheme] = useState(safeFontsTheme)
const updateTheme = useCallback(
() => {
setTheme(themeUI)
document.documentElement.classList.remove(
`font-loading-stage-1`,
`font-loading-stage-2`
)
},
[setTheme],
)
useEventListener(
typeof window !== 'undefined' && window,
'FONTS_ARE_LOADED',
updateTheme
)
useEffect(() => {
updateTheme()
sessionStorage.getItem('areFontsLoaded')
}, [updateTheme])
return (
<Fragment>
{jsx(ThemeProvider, {
theme,
components,
},
element
)}
</Fragment>
)
}
export default ThemeUIProvider
wrap-root-element.js:
/** #jsx jsx */
import { jsx } from 'theme-ui'
import { ColorModeProvider } from '#theme-ui/color-modes'
import PrismThemeProvider from './code-block/prism-theme-provider'
import ThemeUIProvider from './theme-ui-provider'
export const wrapRootElement = ({ element }) => {
return (
<PrismThemeProvider>
<ThemeUIProvider element={element}>
<ColorModeProvider>
{element}
</ColorModeProvider>
</ThemeUIProvider>
</PrismThemeProvider>
)
}
ColorModeButton:
/** #jsx jsx */
import { jsx, IconButton, useColorMode } from 'theme-ui'
const ColorModeButton = props => {
const colorModes = [`default`, `dark`, `deep`, `swiss`]
const [colorMode, setColorMode] = useColorMode()
return (
<IconButton
{...props}
aria-label='Toggle website theme'
onClick={() => {
const index = colorModes.indexOf(colorMode)
const next = colorModes[(index + 1) % colorModes.length]
setColorMode(next)
}}
sx={{
cursor: 'pointer',
padding: 0,
width: 40,
height: 40,
marginX: 1,
}}
>
<svg
width='24'
height='24'
viewBox='0 0 32 32'
fill='currentcolor'
sx={{
display: 'flex',
margin: '0 auto',
transition: 'transform 400ms ease',
}}
>
<circle
cx='16'
cy='16'
r='14'
fill='none'
stroke='currentcolor'
strokeWidth='4'
></circle>
<path d='M 16 0 A 16 16 0 0 0 16 32 z'></path>
</svg>
</IconButton>
)
}
export default ColorModeButton
I have also asked this question on Spectrum and Github issues in case anyone else is interested in checking these threads.

The issue here is that the original gatsby-theme-ui-plugin wasn't removed from package.json and gatsby-config.js.

Related

Having trouble changing pressable color in react native. I had it working without it being an array, am I missing something obvious?

The goal I have is to click on the button and for the button to change color until I click it again. I had the code working with a single button but when I tried to make an array of buttons I ran into trouble, I feel like I misses something obvious but can't find it
//GRID.js
import React, { useState, useEffect } from "react";
import { Cell } from "./cell";
export const Grid = () => {
const ARRAYLENGTH = 3;
const [grid, setGrid] = useState(Array(ARRAYLENGTH).fill({ show: true }));
useEffect(() => {
console.log("updated");
}, [grid]);
const handlePress = (index) => {
let tempGrid = grid;
tempGrid[index].show = !tempGrid[index].show;
setGrid(tempGrid);
console.log(grid[index].show);
logGrid();
};
const logGrid = () => {
console.log(grid);
};
//Renders cell.js
return grid.map((item, index) => {
return (
<Cell
key={`${item} ${index}`}
show={grid[index].show}
index={index}
onClick={handlePress}
/>
);
});
};
//CELL.JS
import React, { useState, useEffect } from "react";
import styled from "styled-components/native";
import { View, Pressable } from "react-native";
import * as theme from "../../config/theme";
//Styles
const StyledCell = styled(Pressable)`
padding: 30px;
border-color: black;
border-width: 1px;
background-color: ${(props) => props.color};
`;
Here is the updated code for anyone who would like to see it
//grid.js
import React, { useState } from "react";
import { Cell } from "./cell";
export const Grid = () => {
const ARRAYLENGTH = 4;
const [grid, setGrid] = useState(
Array(ARRAYLENGTH)
.fill()
.map(() => ({ show: true }))
);
const handlePress = (index) => {
let tempGrid = [...grid];
tempGrid[index].show = !tempGrid[index].show;
setGrid(tempGrid);
};
return grid.map((item, index) => {
return (
<Cell
key={`${item} ${index}`}
show={item.show}
index={index}
onClick={handlePress}
/>
);
});
};
//cell.js
import React, { useState, useEffect } from "react";
import styled from "styled-components/native";
import { View, Pressable } from "react-native";
import * as theme from "../../config/theme";
const StyledCell = styled(Pressable)`
padding: 30px;
border-color: black;
border-width: 1px;
background-color: ${(props) => props.color};
`;
export const Cell = ({ show, onClick, index }) => {
const [color, setColor] = useState(theme.blue);
useEffect(() => {
if (show === true) {
setColor(theme.blue);
} else {
setColor(theme.white);
}
}, [show]);
return (
<View>
<StyledCell
color={color}
onPress={() => {
onClick(index);
}}
/>
</View>
);
};

Styled components + Storybook: SVG Icon is not re-rendering when I pass new styles

I am trying to create a SvgIcon component that I pass an SVG to through the props.
Because of the way React handles SVGs, when they're imported as ReactComponents (e.g. import { ReactComponent as ArrowIcon } from "icons/arrow.svg") I have to define my component in a dynamic way.
This is what I have so far:
const styleSvgIcon = ({ theme, stroke, fill, size, icon: Icon }) => styled(
Icon
).attrs({
viewBox: `0 0 24 24`,
})`
path {
${stroke && `stroke: ${calculateColor({ color: stroke, theme })};`}
${fill && `fill: ${calculateColor({ color: fill, theme })};`}
}
height: ${calculateSize(size)};
width: ${calculateSize(size)};
`;
const SvgIcon = ({ stroke, fill, size, icon }) => {
const theme = useTheme();
const StyledIcon = styleSvgIcon({ theme, stroke, fill, size, icon });
console.log('styledddddddddd', StyledIcon); // prints the styled component with the new styles, but on the page nothing changes
return <StyledIcon />;
};
SvgIcon.propTypes = {
stroke: PropTypes.oneOf(Object.values(SVG_ICON_COLORS)),
fill: PropTypes.oneOf(Object.values(SVG_ICON_COLORS)),
size: PropTypes.oneOf(Object.values(SVG_ICON_SIZES)),
icon: PropTypes.elementType.isRequired,
};
export default SvgIcon;
Here's my story definition:
import React from 'react';
import SvgIcon from './SvgIcon';
import * as all_icons from '../icons';
import { SVG_ICON_SIZES, SVG_ICON_COLORS } from '../constants/variants';
export default {
title: 'media/SvgIcon',
component: SvgIcon,
argTypes: {
size: {
type: 'select',
options: Object.values(SVG_ICON_SIZES),
defaultValue: SVG_ICON_SIZES.SMALL,
},
stroke: {
type: 'select',
options: Object.values(SVG_ICON_COLORS),
defaultValue: SVG_ICON_COLORS.PRIMARY,
},
fill: {
type: 'select',
options: Object.values(SVG_ICON_COLORS),
defaultValue: SVG_ICON_COLORS.PRIMARY,
},
},
};
const Template = (args) => {
const icons = Object.values(all_icons);
// return icons.map((SvgComponent, i) => (
return icons
.slice(0, 2)
.map((SvgComponent, i) => (
<SvgIcon key={i} {...args} icon={SvgComponent} />
));
};
export const Small = Template.bind({});
Small.args = {
size: SVG_ICON_SIZES.SMALL,
};
export const Medium = Template.bind({});
Medium.args = {
size: SVG_ICON_SIZES.MEDIUM,
};
export const Large = Template.bind({});
Large.args = {
size: SVG_ICON_SIZES.LARGE,
};
export const XL = Template.bind({});
XL.args = {
size: SVG_ICON_SIZES.XL,
};
This works fine locally, but when I deploy my app and try to change the props through Storybook, nothing gets updated.
However, in production, the controls do nothing.
I FIGURED IT OUT!!!
So because I was just setting the styles without using the props like this:
size: ${calculate(size)};
styled-components somehow knows and therefore doesn't add listeners to updated props. So my StyledIcon just becomes a static component that does not update when new styles are passed in since they are just set in stone.
Instead, passing them as props and accessing them through props adds those listeners
const SvgIcon = ({ stroke, fill, size, icon: Icon }) => {
const StyledIcon = styled(Icon).attrs({
viewBox: `0 0 24 24`,
})`
path {
stroke: ${({ stroke, theme }) =>
calculateColor({ color: stroke, theme })};
fill: ${({ fill, theme }) => calculateColor({ color: fill, theme })};
}
height: ${({ size }) => calculateSize(size)};
width: ${({ size }) => calculateSize(size)};
`;
return (
<div>
<StyledIcon size={size} fill={fill} stroke={stroke} />,{size},{fill},
{stroke}
<StyledArrowUpIcon stroke={stroke} size={size} fill={fill} />
</div>
);
};

Referring to window size in React Material-UI makeStyles

When I make a custom style for one component, I would have a const like:
const useStyles = makeStyles((theme: Theme) =>
createStyles({
screen: {
margin: 0,
padding: 0
},
surface: {
backgroundColor: theme.palette.primary.main,
height: // use windowSize here
}
})
);
and then do this in the functional component:
const windowSize = useWindowSize(); // { width:number, height:number }
const classes = useStyles();
return (
<Container fixed className={classes.screen}>
<Grid container className={classes.surface}>
<br />
</Grid>
</Container>
When I want to refer the window size (e.g., window height), I can write
<Grid container className={classes.surface} height="75%">
but wouldn't it possible to include that information in makeStyles and compute the number, for example the equivalent to calc(100vw - 100px)?
If I write that calc directly in the makeStyles it would not work.
I have found a library react-use where a hook useWindowSize could handle this issue, but not sure if I can do it. Do I have no way than use the height attribute?
This approach partially works, though it will not update the height in real time when the screen is resized (reloading needed).
Reference: https://github.com/pdeona/responsive-layout-hooks
Next question: Referring to window size in React Material-UI makeStyles in real time
// WindowSizeManager.tsx
import React, { createContext, useContext, ReactNode } from 'react';
import { useWindowSize } from 'react-use';
interface IProps {
children: ReactNode;
size?: { width: number; height: number };
}
export const WindowSizeContext = createContext({ width: 0, height: 0 });
const WindowSizeManager = ({ children }: IProps) => {
const size = useWindowSize();
return (
<WindowSizeContext.Provider value={size}>
{children}
</WindowSizeContext.Provider>
);
};
export const useWindowSizeManager = () => {
return useContext(WindowSizeContext);
};
export default WindowSizeManager;
// SomeComponent.tsx
import React from 'react';
import Container from '#material-ui/core/Container';
import { Grid, makeStyles, Theme, createStyles } from '#material-ui/core';
import { height } from '#material-ui/system';
import { useWindowSizeManager } from './WindowSizemanager';
const useStyles = makeStyles((theme: Theme) =>
createStyles({
screen: {
margin: 0,
padding: 0
},
surface: {
backgroundColor: theme.palette.primary.main,
height: useWindowSizeManager().height - 100
}
})
);
const SomeComponent: React.FC = props => {
const classes = useStyles(props);
return (
<Container fixed className={classes.screen}>
<Grid container className={classes.surface}>
{useWindowSizeManager().height}
</Grid>
</Container>
);
};
export default SomeComponent;

Passing props to MUI styles

Given the Card code as in here. How can I update the card style or any material UI style as from:
const styles = theme => ({
card: {
minWidth: 275,
},
To such follows:
const styles = theme => ({
card: {
minWidth: 275, backgroundColor: props.color
},
when I tried the latest one, I got
Line 15: 'props' is not defined no-undef
when I updated code to be :
const styles = theme => (props) => ({
card: {
minWidth: 275, backgroundColor: props.color
},
also
const styles = (theme ,props) => ({
card: {
minWidth: 275, backgroundColor: props.color
},
Instead of
const styles = theme => ({
card: {
minWidth: 275, backgroundColor: props.color
},
I got the component card style at the web page messy.
By the way, I pass props as follows:
<SimpleCard backgroundColor="#f5f2ff" />
please help!
Deleted the old answer, because it's no reason for existence.
Here's what you want:
import React from 'react';
import { makeStyles } from '#material-ui/core';
const useStyles = makeStyles({
firstStyle: {
backgroundColor: props => props.backgroundColor,
},
secondStyle: {
color: props => props.color,
},
});
const MyComponent = ({children, ...props}) =>{
const { firstStyle, secondStyle } = useStyles(props);
return(
<div className={`${firstStyle} ${secondStyle}`}>
{children}
</div>
)
}
export default MyComponent;
Now you can use it like:
<MyComponent color="yellow" backgroundColor="purple">
Well done
</MyComponent>
Official Documentation
Solution for how to use both props and theme in material ui :
const useStyles = props => makeStyles( theme => ({
div: {
width: theme.spacing(props.units || 0)
}
}));
export default function ComponentExample({ children, ...props }){
const { div } = useStyles(props)();
return (
<div className={div}>{children}</div>
);
}
Here the Typescript solution:
import React from 'react';
import { makeStyles } from '#material-ui/core/styles';
import Button from '#material-ui/core/Button';
import {Theme} from '#material-ui/core';
export interface StyleProps {
height: number;
}
const useStyles = makeStyles<Theme, StyleProps>(theme => ({
root: {
background: 'green',
height: ({height}) => height,
},
}));
export default function Hook() {
const props = {
height: 48
}
const classes = useStyles(props);
return <Button className={classes.root}>Styled with Hook API</Button>;
}
If you want to play with it, try it in this CodeSandbox
In MUI v5, this is how you access the props when creating the style object using styled():
import { styled } from "#mui/material";
const StyledBox = styled(Box)(({ theme, myColor }) => ({
backgroundColor: myColor,
width: 30,
height: 30
}));
For folks who use typescript, you also need to add the prop type to the CreateStyledComponent:
type DivProps = {
myColor: string;
};
const Div = styled(Box)<DivProps>(({ theme, myColor }) => ({
backgroundColor: myColor,
width: 30,
height: 30
}));
<StyledBox myColor="pink" />
If you want to use system props in your custom component like Box and Typography, you can use extendSxProp like the example below:
import { unstable_extendSxProp as extendSxProp } from "#mui/system";
const StyledDiv = styled("div")({});
function DivWithSystemProps(inProps) {
const { sx } = extendSxProp(inProps);
return <StyledDiv sx={sx} />;
}
<DivWithSystemProps
bgcolor="green"
width={30}
height={30}
border="solid 1px red"
/>
Explanation
styled("div")(): Add the sx props to your custom component
extendSxProp(props): Gather the top level system props and put it inside the sx property:
const props = { notSystemProps: true, color: 'green', bgcolor: 'red' };
const finalProps = extendSxProp(props);
// finalProps = {
// notSystemProps: true,
// sx: { color: 'green', bgcolor: 'red' }
// }
To use with typescript, you need to add the type for all system properties:
type DivSystemProps = SystemProps<Theme> & {
sx?: SxProps<Theme>;
};
function DivWithSystemProps(inProps: DivSystemProps) {
const { sx, ...other } = extendSxProp(inProps);
return <StyledDiv sx={sx} {...other} />;
}
Here's the official Material-UI demo.
And here's a very simple example. It uses syntax similar to Styled Components:
import React from "react";
import { makeStyles, Button } from "#material-ui/core";
const useStyles = makeStyles({
root: {
background: props => props.color,
"&:hover": {
background: props => props.hover
}
},
label: { fontFamily: props => props.font }
});
export function MyButton(props) {
const classes = useStyles(props);
return <Button className={classes.root} classes={{ label: classes.label }}>My Button</Button>;
}
// and the JSX...
<MyButton color="red" hover="blue" font="Comic Sans MS" />
This demo uses makeStyles, but this feature is also available in styled and withStyles.
This was first introduced in #material-ui/styles on Nov 3, 2018 and was included in #material-ui/core starting with version 4.
This answer was written prior to version 4.0 severely out of date!
Seriously, if you're styling a function component, use makeStyles.
The answer from James Tan is the best answer for version 4.x
Anything below here is ancient:
When you're using withStyles, you have access to the theme, but not props.
Please note that there is an open issue on Github requesting this feature and some of the comments may point you to an alternative solution that may interest you.
One way to change the background color of a card using props would be to set this property using inline styles. I've forked your original codesandbox with a few changes, you can view the modified version to see this in action.
Here's what I did:
Render the component with a backgroundColor prop:
// in index.js
if (rootElement) {
render(<Demo backgroundColor="#f00" />, rootElement);
}
Use this prop to apply an inline style to the card:
function SimpleCard(props) {
// in demo.js
const { classes, backgroundColor } = props;
const bull = <span className={classes.bullet}>•</span>;
return (
<div>
<Card className={classes.card} style={{ backgroundColor }}>
<CardContent>
// etc
Now the rendered Card component has a red (#F00) background
Take a look at the Overrides section of the documentation for other options.
#mui v5
You can use styled() utility (Make sure that you're importing the correct one) and shouldForwardProp option.
In the following example SomeProps passed to a div component
import { styled } from '#mui/material'
interface SomeProps {
backgroundColor: 'red'|'blue',
width: number
}
const CustomDiv = styled('div', { shouldForwardProp: (prop) => prop !== 'someProps' })<{
someProps: SomeProps;
}>(({ theme, someProps }) => {
return ({
backgroundColor: someProps.backgroundColor,
width: `${someProps.width}em`,
margin:theme.spacing(1)
})
})
import React from "react";
import { makeStyles } from "#material-ui/styles";
import Button from "#material-ui/core/Button";
const useStyles = makeStyles({
root: {
background: props => props.color,
"&:hover": {
background: props => props.hover
}
}
});
export function MyButton(props) {
const classes = useStyles({color: 'red', hover: 'green'});
return <Button className={classes.root}>My Button</Button>;
}
Was missing from this thread a props use within withStyles (and lead to think it wasn't supported)
But this worked for me (say for styling a MenuItem):
const StyledMenuItem = withStyles((theme) => ({
root: {
'&:focus': {
backgroundColor: props => props.focusBackground,
'& .MuiListItemIcon-root, & .MuiListItemText-primary': {
color: props => props.focusColor,
},
},
},
}))(MenuItem);
And then use it as so:
<StyledMenuItem focusColor={'red'} focusBackground={'green'}... >...</StyledMenuItem>
I spent a couple of hours trying to get withStyles to work with passing properties in Typescript. None of the solutions I found online worked with what I was trying to do, so I ended up knitting my own solution together, with snippets from here and there.
This should work if you have external components from, lets say Material UI, that you want to give a default style, but you also want to reuse it by passing different styling options to the component:
import * as React from 'react';
import { Theme, createStyles, makeStyles } from '#material-ui/core/styles';
import { TableCell, TableCellProps } from '#material-ui/core';
type Props = {
backgroundColor?: string
}
const useStyles = makeStyles<Theme, Props>(theme =>
createStyles({
head: {
backgroundColor: ({ backgroundColor }) => backgroundColor || theme.palette.common.black,
color: theme.palette.common.white,
fontSize: 13
},
body: {
fontSize: 12,
},
})
);
export function StyledTableCell(props: Props & Omit<TableCellProps, keyof Props>) {
const classes = useStyles(props);
return <TableCell classes={classes} {...props} />;
}
It may not be the perfect solution, but it seems to work. It's a real bugger that they haven't just amended withStyles to accept properties. It would make things a lot easier.
Solution for TypeScript with Class Component:
type PropsBeforeStyle = {
propA: string;
propB: number;
}
const styles = (theme: Theme) => createStyles({
root: {
color: (props: PropsBeforeStyle) => {}
}
});
type Props = PropsBeforeStyle & WithStyles<typeof styles>;
class MyClassComponent extends Component<Props> {...}
export default withStyles(styles)(MyClassComponent);
export const renderButton = (tag, method, color) => {
const OkButton = withStyles({
root: {
"color": `${color}`,
"filter": "opacity(0.5)",
"textShadow": "0 0 3px #24fda39a",
"backgroundColor": "none",
"borderRadius": "2px solid #24fda3c9",
"outline": "none",
"border": "2px solid #24fda3c9",
"&:hover": {
color: "#24fda3c9",
border: "2px solid #24fda3c9",
filter: "opacity(1)",
},
"&:active": {
outline: "none",
},
"&:focus": {
outline: "none",
},
},
})(Button);
return (
<OkButton tag={tag} color={color} fullWidth onClick={method}>
{tag}
</OkButton>
);
};
renderButton('Submit', toggleAlert, 'red')
Here's another way of dynamically passing props to hook's API in MUI v5
import React from "react";
import { makeStyles } from "#mui/styles";
import { Theme } from "#mui/material";
interface StyleProps {
height: number;
backgroundColor: string;
}
const useStyles = makeStyles<Theme>((theme) => ({
root: ({ height, backgroundColor }: StyleProps) => ({
background: backgroundColor,
height: height
})
}));
export default function Hook() {
const props = {
height: 58,
backgroundColor: "red"
};
const classes = useStyles(props);
return (
<button className={classes.root}>
another way of passing props to useStyle hooks
</button>
);
}
here's the codesandbox https://codesandbox.io/s/styles-with-props-forked-gx3bf?file=/demo.tsx:0-607
Here's 2 full working examples of how to pass props to MUI v5 styles. Either using css or javascript object syntax.
With css syntax:
import { styled } from '#mui/system'
interface Props {
myColor: string
}
const MyComponent = ({ myColor }: Props) => {
const MyStyledComponent = styled('div')`
background-color: ${myColor};
.my-paragraph {
color: white;
}
`
return (
<MyStyledComponent >
<p className="my-paragraph">Hello there</p>
</MyStyledComponent >
)
}
export default MyComponent
Note that we define MyStyledComponent within MyComponent, making the scoped props available to use in the template string of the styled() function.
Same thing with javascript object syntax:
import { styled } from '#mui/system'
const MyComponent = ({ className }: any) => {
return (
<div className={className}>
<p className="my-paragraph">Hello there</p>
</div>
)
}
interface Props {
myColor: string
}
const MyStyledComponent = styled(MyComponent)((props: Props) => ({
backgroundColor: props.myColor,
'.my-paragraph': { color: 'white' },
}))
export default MyStyledComponent
For this second example, please note how we pass the className to the component we want to apply the styles to. The styled() function will pass a className prop with the styles you define. You typically want to apply that to your root element. In this case the div.
Result:
I'm sure there's other variations of how to do this, but these two are easy to implement and understand.
You might need to memoize the calculated styles, and maybe not use this approach if your props changes a lot. I don't think it's very performant.
Using styled-components, sometimes you only want to apply styles if the prop is passed, in such cases you can do something like this (remove : {foo: boolean} if not using TypeScript):
const SAnchor = styled("a", {
shouldForwardProp: prop => prop !== "foo",
})(({foo = false}: {foo: boolean}) => ({
...(foo && {
color: "inherit",
textDecoration: "none",
}),
}));
Object spread source
shouldForwardProp docs
#mui v5
I use theme and prop from JSON object
const Answerdiv = styled((props) => {
const { item_style, ...other } = props;
return <div {...other}></div>;
})(({ theme, item_style }) => {
for(var i of Object.keys(item_style)){
item_style[i] =Object.byString(theme,item_style[i])|| item_style[i];
}
return (item_style)
});
component use
<Answerdiv item_style={(item_style ? JSON.parse(item_style) : {})}>
for Object.byString
Object.byString = function(o, s) {
s = s.replace(/\[(\w+)\]/g, '.$1'); // convert indexes to properties
s = s.replace(/^\./, ''); // strip a leading dot
var a = s.split('.');
for (var i = 0, n = a.length; i < n; ++i) {
var k = a[i];
if (k in o) {
o = o[k];
} else {
return;
}
}
return o;
}

react-dnd not detecting hover and drop events

I'm having trouble getting react-dnd to to work. Specifically, while I can confirm dragging is being detected properly, my droppable targets are not detecting hover or drop events. I've been following the example at http://survivejs.com/react/implementing-kanban/drag-and-drop/ to create a draggable item. I've tried to use a combination of the same examples and the official examples from the official repo to create a DropTarget to accept the draggable. However, my DropTarget is giving no indication that it is detecting the draggable. My code below has multiple debugger statements to indicate if code is being reached, but none of them ever are.
I suspect that the compose call at the end might be the problem, but I'm following Dan Abramov's example here. Just to add to the problem, the React inspector in Chrome dev tools lets me see the itemType state variable change as I drag an item. However, both the canDrop and isOver state variables remain false. I'd appreciate any help to get this to work.
import { findDOMNode } from 'react-dom';
import React, { Component } from 'react';
import MuiThemeProvider from 'material-ui/styles/MuiThemeProvider';
import Paper from 'material-ui/Paper';
import FaDelete from 'react-icons/lib/fa/trash-o';
import RaisedButton from 'material-ui/RaisedButton';
import FaEdit from 'react-icons/lib/fa/star';
import actions from '../actions/actions';
import TextField from 'material-ui/TextField';
import { connect } from 'react-redux';
//import EmojiPickerPopup from './EmojiPickerPopup';
import RenderIf from 'render-if';
import globals from '../globals';
import { DropTarget } from 'react-dnd';
import { compose } from 'redux';
const locationItemContainer = {
display: 'flex',
flexDirection: 'column',
backgroundColor: 'lightgoldenrodyellow',
border: '1px solid Moccasin',
width: "33%",
maxHeight: "15em"
}
const controlsContainer = {
display: 'flex',
flexDirection: 'row',
justifyContent: 'space-around',
width: "100%"
}
const paperStyle = {
padding: '8px 4px',
display: 'flex',
flexDirection: "column",
alignItems: 'center',
justifyContent: 'center',
width: "100%",
height: "100%"
};
class LocationItemComponent extends Component {
constructor(props, context) {
super(props, context);
this.state = {
locationMarkers: []
}
}
componentWillReceiveProps(nextProps) {
if (!this.props.isOver && nextProps.isOver) {
// You can use this as enter handler
debugger
}
if (this.props.isOver && !nextProps.isOver) {
// You can use this as leave handler
debugger
}
if (this.props.isOverCurrent && !nextProps.isOverCurrent) {
// You can be more specific and track enter/leave
// shallowly, not including nested targets
debugger
}
}
nameChanged = (id, event, value) => {
this.props.dispatch(actions.storyMapActions.updateMarkerName(value, id));
}
deleteMarker = (id) => {
this.props.dispatch(actions.storyMapActions.deleteMarker(id));
}
showEmojiPicker = (id, event) => {
this.props.dispatch(actions.modalsActions.showEmojiPicker(id, event.currentTarget))
}
render() {
const { isOver, canDrop, connectDropTarget } = this.props;
if (isOver) {
console.log("is over");
}
return connectDropTarget(
<div style={locationItemContainer}>
<MuiThemeProvider>
<Paper zDepth={5}
style={paperStyle}
rounded={false}>
<TextField
id="markerName"
hintText="marker Name"
onChange={this.nameChanged.bind(this, this.props.marker.id)}
value={this.props.marker.name}
underlineFocusStyle={{ color: globals.textUnderlineColor }}
/>
<div style={controlsContainer}>
<RaisedButton
icon={<FaEdit />}
primary={true}
onClick={this.showEmojiPicker.bind(this, this.props.marker.id)} />
<RaisedButton
icon={<FaDelete />}
secondary={true}
onClick={this.deleteMarker.bind(this, this.props.marker.id)} />
</div>
</Paper>
</MuiThemeProvider>
</div>
);
}
}
const mapStateToProps = (state) => {
return Object.assign({}, { state: state });
}
const locationTarget = {
canDrop(props, monitor) {
debugger;
// You can disallow drop based on props or item
const item = monitor.getItem();
return true;
},
hover(props, monitor, component) {
debugger;
// This is fired very often and lets you perform side effects
// in response to the hover. You can't handle enter and leave
// here—if you need them, put monitor.isOver() into collect() so you
// can just use componentWillReceiveProps() to handle enter/leave.
// You can access the coordinates if you need them
const clientOffset = monitor.getClientOffset();
const componentRect = findDOMNode(component).getBoundingClientRect();
// You can check whether we're over a nested drop target
const isJustOverThisOne = monitor.isOver({ shallow: true });
// You will receive hover() even for items for which canDrop() is false
const canDrop = monitor.canDrop();
},
drop(props, monitor, component) {
debugger;
if (monitor.didDrop()) {
// If you want, you can check whether some nested
// target already handled drop
debugger
return;
}
// Obtain the dragged item
const item = monitor.getItem();
// You can do something with it
//ChessActions.movePiece(item.fromPosition, props.position);
// You can also do nothing and return a drop result,
// which will be available as monitor.getDropResult()
// in the drag source's endDrag() method
return { moved: true };
}
};
const collect = (connect, monitor) => {
return {
connectDropTarget: connect.dropTarget(),
isOver: monitor.isOver(),
canDrop: monitor.canDrop(),
itemType: monitor.getItemType()
};
}
export default compose(
connect(mapStateToProps),
DropTarget(globals.itemTypes.LOCATION_ITEM, locationTarget, collect)
)(LocationItemComponent);

Resources