React-Beautiful-Dnd Can't Find Draggable Element with Id - reactjs

I'm trying to replicate the react-beautiful-dnd tutorial step 4: reorder a list. I've copied the code in the tutorial as far as I can see exactly: https://egghead.io/lessons/react-reorder-a-list-with-react-beautiful-dnd
However, when I run react and try to drag the list items, I get errors like: Unable to find draggable element with id: task-1
If I look at the DOM, I can definitely see an element with that id:
I can't figure out what I'm doing wrong. I printed the id to console to check that it's a string, and it is. Thoughts?
INITIAL-DATA.JS
const initialData = {
tasks : {
"task-1" : { id : "task-1", content : "Take out garbage"},
"task-2" : { id : "task-2", content : "Watch show"},
"task-3" : { id : "task-3", content : "Charge phone"},
"task-4" : { id : "task-4", content : "Cook dinner"},
},
columns : {
"column-1" : {
id : "column-1",
title: "To Do",
taskIds : ["task-1", "task-2", "task-3", "task-4"]
}
},
columnOrder : ["column-1"]
};
export default initialData;
INDEX.JS
import React from 'react';
import ReactDOM from 'react-dom';
import initialData from "./initial-data";
import Column from "./column";
import { DragDropContext } from 'react-beautiful-dnd';
class App extends React.Component {
state = initialData;
// Needs to synchronously update our state to reflect the drag-drop result
// The only required DragDrop callback
onDragEnd = result => {
}
render() {
return (
<DragDropContext onDragEnd={this.onDragEnd}>
{
this.state.columnOrder.map( (columnId) => {
const column = this.state.columns[columnId];
const tasks = column.taskIds.map( taskId => this.state.tasks[taskId]);
return <Column key={column.id} column={column} tasks={tasks} />;
})
}
</DragDropContext>
)
}
}
ReactDOM.render(
<App />,
document.getElementById('root')
);
COLUMN.JS
import React from "react";
import styled from "styled-components";
import Task from "./task";
import { Droppable } from 'react-beautiful-dnd';
const Container = styled.div`
margin: 8px;
border: 1px solid lightgrey;
border-radius: 2px;
`;
const Title = styled.h3`
padding: 8px;
margin: 0px;
`;
const TaskList = styled.div`
padding: 8px;
`;
export default class Column extends React.Component {
render() {
return (
// Note: Droppables expect their child to be a function that returns a react component
<Container>
<Title>{this.props.column.title}</Title>
<Droppable droppableId={this.props.column.id}>
{ provided => (
// The droppableProps in the provided object (a react-beautiful-dnd object) need to get provided to the object
// you want to designate as your droppable
// {provided.placeholder} // Needs to be added as a child of the component you designate as the droppable
// ref is an attribute of -components. Returns the dom node of the component
<TaskList
ref={provided.innerRef}
{...provided.droppableProps}
>
{ this.props.tasks.map( (task, index) => <Task key={task.id} task={task} index={index} /> ) }
{provided.placeholder}
</TaskList>
)}
</Droppable>
</Container>
)
}
}
TASK.JS
import React from "react";
import styled from "styled-components";
import { Draggable } from 'react-beautiful-dnd';
const Container = styled.div`
border: 1px solid lightgrey;
border-radius: 2px;
padding: 8px;
margin-bottom: 8px;
background-color: white; /* so don't see through when dragging */
`;
export default class Task extends React.Component {
render() {
console.log(this.props.task.id)
console.log(typeof this.props.task.id)
return (
// Required draggable props are draggableId and index
// Note: Draggables expect their child to be a function that returns a react component
<Draggable draggableId={this.props.task.id} index={this.props.index}>
{ provided => (
// The draggbleProps in the provided object (a react-beautiful-dnd object) need to get provided to the object
// you want to designate as your draggable
// DragHandleProps needs to get applied to the part of that object that you want to use to drag the whole object
// ref is an attribute of -components. Returns the dom node of the component
<Container
ref={provided.innerRef}
{...provided.draggbleProps}
{...provided.dragHandleProps}
>
{ this.props.task.content }
</Container>
)}
</Draggable>
)
}
}

There is just a typo in your code: in task.js change {...provided.draggbleProps} to {...provided.draggableProps}

As seen above, the issue here was the typo. Per your comment below that answer, you could help avoid this in the future by using Typescript. It would have shown you an error in your editor at the typo, and also given you autocomplete.

Related

.map() in reactJs is not returning anything

i'm new to react and im trying to add render a list of items from an external SidebarData.js file (in the same root /components/..)
i'm not sure why my map function is not returning anything.
i get a list of elements thats correct, but the item.title and item.path seem not to render...
I feel there's a problem with the props.
I tried to write just
render(){
<h1>{SubmenuData[1].title}</h1>
}
and it works fine, but when i try to map on the full array, it doesn't seem to render anything. it renders the correct number of elements, but the title and path are not returning...
Here's my two components : Sidebar (Main one)
import React from 'react'
import styled from 'styled-components'
import { SidebarData } from './SidebarData'
import Submenu from './Submenu'
const Nav = styled.div`
background: #f5f5f5;
color: #7d7d7d;
display:flex;
justify-content:flex-start;
height:100%;
width:15%;
`
const Sidebar = () => {
return (
<>
<Nav>
{SidebarData.map((item, index)=>{
return <Submenu item={item} key={item.index} />
})}
</Nav>
</>
)
}
export default Sidebar (Where i think there's a problem)
and Submenu
import React, { Component } from 'react'
import styled from 'styled-components'
import { Link } from "react-router-dom"
const SidebarLink = styled(Link)`
display: flex;
color: #404040;
`
const SidebarLabel = styled.span`
color:#000;
`
const Submenu = (item)=>{
return (
<SidebarLink to={item.path} >
<SidebarLabel>{item.title}</SidebarLabel>
</SidebarLink>
)
}
export default Submenu
Your style of receiving props is mistake i guess. Destructure the props like:
const Submenu = ({item})=>{
return (
<SidebarLink to={item.path} >
<SidebarLabel>{item.title}</SidebarLabel>
</SidebarLink>
)
}
export default Submenu

How to add a class name to an input on a SimpleSchema AutoForm in react?

I want to add a class name for an individual input, but I could not find anything on the docs about a class name property for simpl-schema or uniforms-bridge-simple-schema-2.
import { AutoForm } from "uniforms-antd";
import SimpleSchemaBridge from "uniforms-bridge-simple-schema-2";
import SimpleSchema from "simpl-schema";
const rawSchema = {
name: {
type: String,
optional: true,
// className - how to add a class name for an individual input?
}
};
const schema = new SimpleSchemaBridge(new SimpleSchema(rawSchema));
return (
<AutoForm
schema={schema}
model={model ? model : {}}
onSubmit={(values: any) => {
onUpdate(values);
}}
/>
);
I have not explored all the APIs of uniforms. I found this way you can add classNames to each field.
Their FAQ says :
How can I customize/style my form fields?
You can style your form
fields simply by passing a className property.
App
import React from "react";
import "./styles.css";
import { AutoForm, AutoField, SubmitField, ErrorsField } from "uniforms-antd";
import SimpleSchemaBridge from "uniforms-bridge-simple-schema-2";
import SimpleSchema from "simpl-schema";
export default function App() {
const rawSchema = {
name: {
type: String,
optional: false,
//className: 'test'// - not working
// className - how to add a class name for an individual input?
}
};
const schema = new SimpleSchemaBridge(new SimpleSchema(rawSchema));
return (
<div className="App">
<AutoForm schema={schema} >
<AutoField name="name" className="test"/>
<ErrorsField className="my-error"/>
<SubmitField className="my-submit"/>
</AutoForm>
</div>
);
}
css
.test{
border: 1px solid navy;
border-radius: 12px;
height: 45px;
font-size: 18px;
}
.my-error{
}
.my-submit{
}
Notes:
1) Check out all the fields of uniforms-antd.
2) Customizing your form layout

Passing custom props to each styled component through Provider

I would like to pass a custom prop (exactly: theme name as string) to each passed styled component through Provider, so it was available throughout the css definition.
ThemeProvider almost does it, but it expects object, not the string. I do not want to pass whole object with theme settings, just the name of my theme.
I do not want to use special theme prop or similar, because then I would have to it manually every single time I create new styled component. Provider seems like the best option if only it cooperated with string.
Is there any possibility to pass a string through Provider to Consumer builded in styled components?
EDIT:
[PARTIAL SOLUTION]
I found what I was looking for when I realized styled-components exports their inner context. That was it. Having access to pure react context gives you original Provider, without any 'only objects' restriction ('only objects' is a styled-components custom provider restriction).
Now I can push to each styled component exactly what I want and if I want.
import styled, { ThemeContext } from 'styled-components';
const StyledComponent = styled.div`
color: ${props => props.theme == 'dark' ? 'white' : 'black'};
`;
const Component = props => {
const theme = 'dark';
return (
<ThemeContext.Provider value={theme}>
<NextLevelComponent>
<StyledComponent />
</NextLevelComponent>
</ThemeContext.Provider>
);
};
Hope I have this correct, from what I've been able to glean. I haven't tried this out but it seems it might work for you. This is lifted directly from the reactjs.org docs regarding context. It passed the string name of the theme down.
const ThemeContext = React.createContext('green');
class App extends React.Component {
render() {
return (
<ThemeContext.Provider value="blue">
<SomeComponent />
</ThemeContext.Provider>
);
}
}
function SomeComponent(props) {
return (
<div>
<OtherComponent />
</div>
);
}
class OtherComponent extends React.Component {
static contextType = ThemeContext;
render() {
return <ThirdComponent theme={this.context} />
}
}
I hope this helps you understand the idea behind ThemeContext from styled-components. I've passed string "blue" to ThemeContext just to show, that it should not be object and you can use just string.
import React, { useContext } from "react";
import ReactDOM from "react-dom";
import styled, { ThemeContext } from "styled-components";
// Define styled button
const Button = styled.button`
font-size: 1em;
margin: 1em;
padding: 0.25em 1em;
border-radius: 3px;
color: ${props => props.theme};
border: 2px solid ${props => props.theme};
`;
// Define the name of the theme / color (string)
const themeName = "blue";
const ThemedButton = () => {
// Get the name from context
const themeName = useContext(ThemeContext);
return <Button theme={themeName}>Themed color: {themeName}</Button>;
};
function App() {
return (
<div className="App">
<ThemeContext.Provider value={themeName}>
<ThemedButton />
</ThemeContext.Provider>
</div>
);
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
Demo: https://codesandbox.io/s/styled-components-example-with-themecontext-cso55

react-custom-scrollbars jumps to top on any action

I am using react-custom-scrollbars in a react web app because I need to have two independant scroll bars, one for the main panel and one for the drawer panel. My issue is that the content in the main panel is dynamic and whenever I take some action in the main panel that changes state the scroll bar jumps to the top of the panel again.
UPDATE:
I believe I need to list for onUpdate and handle the scroll position there. If it has changed then update if not do not move the position?
In code, I have a HOC call withScrollbar as follows:
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import styled from 'styled-components';
import AutoSizer from 'react-virtualized-auto-sizer';
import { Scrollbars } from 'react-custom-scrollbars';
import { colors } from '../../theme/vars';
import { themes } from '../../types';
// This is a Higher Order Component (HOC) used to
// provide a scroll bar to other components
export default (ChildComponent, styling) => {
class ComposedComponent extends Component {
state = {
// position: ??
};
handleUpdate = () => {
//update position
//this.scrollbar.scrollToBottom();
};
render() {
return (
<AutoSizer>
{
({ width, height }) => (
<Scrollbars
style={{ width, height, backgroundColor: colors.WHITE, overflow: 'hidden', ...styling }}
onUpdate={() => this.handleUpdate()}
renderThumbVertical={props => <Thumb {...props} />}
autoHide
autoHideTimeout={1000}
autoHideDuration={200}
>
<ChildComponent {...this.props} />
</Scrollbars>
)
}
</AutoSizer>
);
}
}
return ComposedComponent;
};
const Thumb = styled.div`
background-color: ${props =>
props.theme.theme === themes.LIGHT ? colors.BLACK : colors.WHITE};
border-radius: 4px;
`;
in my MainView component I just wrap the export like this:
export default withScrollbar(LanguageProvider(connect(mapStateToProps, null)(MainView)));
I have read a few similar issues on this like this one: How to set initial scrollTop value to and this one scrollTo event but I cannot figure out how to implement in my case. Any tips or suggestions are greatly appreciated.
So I found a way to get this to work and it feels like a complete hack but I'm posting in hopes it might help someone else.
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import styled from 'styled-components';
import AutoSizer from 'react-virtualized-auto-sizer';
import { Scrollbars } from 'react-custom-scrollbars';
import { colors } from '../../theme/vars';
import { themes } from '../../types';
// This is a Higher Order Component (HOC) used to
// provide a scroll bar to other components
export default (ChildComponent, styling) => {
class ComposedComponent extends Component {
state = {
stateScrollTop: 0,
};
onScrollStart = () => {
if (this.props.childRef) { // need to pass in a ref from the child component
const { scrollTop } = this.props.childRef.current.getValues();
const deltaY = Math.abs(scrollTop - this.state.stateScrollTop);
if (deltaY > 100) { // 100 is arbitrary. It should not be a high value...
this.props.childRef.current.scrollTop(this.state.stateScrollTop);
}
}
};
handleUpdate = () => {
if (this.props.childRef) {
const { scrollTop } = this.props.childRef.current.getValues();
this.setState({ stateScrollTop: scrollTop });
}
};
render() {
return (
<AutoSizer>
{
({ width, height }) => (
<Scrollbars
ref={this.props.childRef}
style={{ width, height, backgroundColor: colors.WHITE, overflow: 'hidden', ...styling }}
onScrollStart={e => this.onScrollStart(e)}
onUpdate={e => this.handleUpdate(e)}
renderThumbVertical={props => <Thumb {...props} />}
autoHide
autoHideTimeout={1000}
autoHideDuration={200}
>
<ChildComponent {...this.props} />
</Scrollbars>
)
}
</AutoSizer>
);
}
}
return ComposedComponent;
};
const Thumb = styled.div`
background-color: ${props =>
props.theme.theme === themes.LIGHT ? colors.BLACK : colors.WHITE};
border-radius: 4px;
`;
I use this HOC like this:
create a ref for the component you want to use it with
pass the ref to the component that will use the HOC:
class SomeChildComponent extends Component {
...
viewRef = React.createRef();
...
render() {
return ( <MainView childRef={this.viewRef} />)
}
import and wrap the component
import withScrollbar from '../../hoc/withScrollbar';
...
export default withScrollbar(MainView);
I tried the above solution and it didn't seem to work for me.
However what did work was making sure that my child components inside Scrollbars were wrapped in a div with a height of 100%:
<Scrollbars>
<div style={{ height: '100%' }}>
<ChildComponent />
<ChildComponent />
</div>
</Scrollbars>

How to use the styled-component property innerRef with a React stateless component?

I have the following styled component:
const Component = styled.div`
...
`;
const Button = (props) => {
return (
<Component>
...
</Component>
);
};
export default styled(Button)``;
I want to get a reference to the underlying div of Component. When I do the following I get null:
import Button from './Button.js';
class Foo extends React.Component {
getRef = () => {
console.log(this.btn);
}
render() {
return (
<Button innerRef={elem => this.btn = elem} />
);
}
}
Any ideas why I am getting null and any suggestions on how to access the underlying div?
Note: The reason I am doing this export default styled(Button)``; is so that the export styled component can be easily extended.
I managed to accomplish this by passing a function down as a prop to the styled-component that I was targeting, then passing the ref back as an argument of the function:
const Component = styled.div`
...
`;
const Button = (props) => {
return (
<Component innerRef={elem => props.getRef(elem)}>
...
</Component>
);
};
export default styled(Button)``;
...
import Button from './Button.js';
class Foo extends React.Component {
getRef = (ref) => {
console.log(ref);
}
render() {
return (
<Button getRef={this.getRef} />
);
}
}
Passing a ref prop to a styled component will give you an instance of the StyledComponent wrapper, but not to the underlying DOM node. This is due to how refs work. So it's not possible to call DOM methods, like focus, on styled components wrappers directly.
Thus to get a ref to the actual, wrapped inner DOM node, callback is passed to the innerRef prop as shown in example below to focus the 'input' element wrapped inside Styled component 'Input' on hover.
const Input = styled.input`
padding: 0.5em;
margin: 0.5em;
color: palevioletred;
background: papayawhip;
border: none;
border-radius: 3px;
`;
class Form extends React.Component {
render() {
return (
<Input
placeholder="Hover here..."
innerRef={x => { this.input = x }}
onMouseEnter={() => this.input.focus()}
/>
);
}
}
render(
<Form />
);
NOTE:-
String refs not supported (i.e. innerRef="node"), since they're already deprecated in React.

Resources