I want to learn React Beautiful Dnd by coding two div boxes that contain child elements I can drag between them. All (most?) tutorials online are Trello clones with loops and state that clutter my understanding of the basics. I want to simplify my understanding by hard coding only the most minimal state required with the end result being two components (the component name is "Column") each that contain a div (A component named "Task") that I can drag to the other.
At the moment. I am getting the error " TypeError: children is not a function ".
Here is my code:
src/App.js
import {DragDropContext} from 'react-beautiful-dnd';
import Column from './Components/Column';
function App() {
return (
<DragDropContext onDropEnd={result => console.log("my life is worth more than this")}>
<Column id="1"/>
</DragDropContext>
);
}
export default App;
src/Components/Column
import React from 'react';
import {Droppable} from 'react-beautiful-dnd';
import Task from "../../Components/Task"
function Column(props){
const { classes, id } = props;
let style = {
backgroundColor:"orange",
height:"100px",
width:"100px",
margin:"100px"
}
return (
<Droppable droppable = {id}>
{provided => (
<div {...provided.droppableProps} ref={provided.innerRef} style={style}>
Column is orange task is red
<Task id="1"/>
<Task id="2"/>
{provided.placeholder}
</div>
)
}
</Droppable>
)
}
export default Column
src/Components/Task
import React from 'react';
import {Draggable} from 'react-beautiful-dnd';
function Task(props){
const { classes, id } = props;
return (
<Draggable draggableId ={id}>
<div>
some task
</div>
</Draggable>
)
}
export default Task
Here is a basic working example of simply dragging items (in case anyone is looking for it). I've decided to document my learning here because I feel remedial examples are lacking in the official docs. I like "hello world" examples.
The first thing to realize is that using the library requires understanding three components. Each component has it's respective boilerplate code.
They are (buried down the page) in the official docs.
<DragDropContext />
- Wraps the part of your application you want to have drag and drop enabled for
<Droppable />
- An area that can be dropped into. Contains components
<Draggable />
- What can be dragged around
Your app needs to be wrapped in a single DragDropContext (multiple DragDropContext are not supported). This example has minimal state.The id properties in the state objects are required (they can be named different but are required none the less).
src/App.js
import React,{useState} from 'react';
import {DragDropContext} from 'react-beautiful-dnd';
import Column from './Components/Column';
function App() {
const [listOne, setListOne] = useState([{id:"1", title:"Test-1"},{id:"2", title:"Test-2"}]);
const [listTwo, setListTwo] = useState([{id:"3", title:"Test-3"},{id:"4", title:"Test-4"}]);
return (
<DragDropContext onDropEnd={result => console.log(result)}>
<Column id="1" list = {listOne}/>
<Column id="2" list = {listTwo}/>
<div> context hello world </div>
</DragDropContext>
);
}
export default App;
<Droppable/> components nest <Draggable/> components. This returned function boiler plate code is required:
{provided => (
)}
Explanation for each property of provided is here
src/Components/Column
import React from 'react';
import {Droppable} from 'react-beautiful-dnd';
import Task from "../../Components/Task"
function Column(props){
const { classes, id, list } = props;
let style = {
backgroundColor:"orange",
height:"300px",
width:"400px",
margin:"100px"
}
console.log(list)
return (
<Droppable droppableId = {id}>
{provided => (
<div {...provided.droppableProps} ref={provided.innerRef} style={style}>
{list.map((val,index)=>{
return <Task id={val.id} key={index} index={index} title={val.title}/>
})}
{provided.placeholder}
</div>
)
}
</Droppable>
)
}
export default Column
src/Components/Task
import React from 'react';
import {Draggable} from 'react-beautiful-dnd';
function Task(props){
const { classes, id, index,title } = props;
let style = {
backgroundColor:"red",
}
return (
<Draggable draggableId ={id} index={index} >
{(provided) => (
<div
ref={provided.innerRef}
{...provided.draggableProps}
{...provided.dragHandleProps}
>
<h4 style={style}>{title}</h4>
</div>
)}
</Draggable>
)
}
export default Task
Related
Hi do you render a react elements from an array of strings using object literal to get its value? let say for example I got an array of strings and I want to mapped on it using a function that returns an object literal with predefined values, if the strings matched return the react element. Here's what i've tried but doesn't display the icons
import { useCallback, useEffect, useMemo } from "react";
import PersonIcon from "#mui/icons-material/Person"
import ShoppingCartIcon from '#mui/icons-material/ShoppingCart'
import SecurityIcon from '#mui/icons-material/Security'
import LocalActivityIcon from '#mui/icons-material/LocalActivity'
export default function App() {
const iconNames = useMemo(()=>['PersonIcon','ShoppingCartIcon', 'SecurityIcon', 'LocalActivityIcon'],[]);
const getIcons = (icon) =>{
const icons = {
PersonIcon: <PersonIcon />,
ShoppingCartIcon: <ShoppingCartIcon />,
SecurityIcon: <SecurityIcon />,
LocalActivityIcon: <LocalActivityIcon />
}
return icons[icon];
}
const displayIcons = useCallback((arr) => {
return <ul>{arr.map((icon) => { return getIcons[icon] })}</ul>
},[])
useEffect(()=>{
displayIcons(iconNames)
})
return (
<div className="App">
<h1>Icons</h1>
{displayIcons}
</div>
);
}
Assuming that the icons are imported and static, perhaps the icons object could be defined outside of the component, so that even without useMemo or useCallback, it would not be re-created when the component re-renders.
The getIcons might not be necessary to access the icons in the object, as in the output JSX, the icons[name] can be wrapped in a curly braces {} to render the icon.
Example (live demo on: stackblitz):
import PersonIcon from '#mui/icons-material/Person';
import ShoppingCartIcon from '#mui/icons-material/ShoppingCart';
import SecurityIcon from '#mui/icons-material/Security';
import LocalActivityIcon from '#mui/icons-material/LocalActivity';
// 👇 Can be defined here if the icons are imported and static
const icons = {
PersonIcon: <PersonIcon />,
ShoppingCartIcon: <ShoppingCartIcon />,
SecurityIcon: <SecurityIcon />,
LocalActivityIcon: <LocalActivityIcon />,
};
// 👇 Just an array of the icon names for testing
const iconNames = Object.keys(icons);
export default function App() {
return (
<div className="App">
<h1>Icons</h1>
<ul>
{iconNames.map((name, index) => (
<li key={index}>{icons[name]}</li>
))}
</ul>
</div>
);
}
However depending on the use case, perhaps also consider to use other approaches instead of accessing it from an object to display a list of icons.
The example below displays a list of icons, and could also pass the MUI props to style it if needed (may not work for other libraries).
Example (live demo on: stackblitz):
// Works for MUI icons but might not for other libraries
const icons = [PersonIcon, ShoppingCartIcon, SecurityIcon, LocalActivityIcon];
export default function App() {
return (
<div className="App">
<h1>Icons</h1>
<ul>
{icons.map((Icon, index) => (
<li key={index}>
<Icon fontSize="large" color="primary" />
</li>
))}
</ul>
</div>
);
}
Just another possible approach that also works (with MUI) and accepts props for the icons, by defining the icon as component inside map:
Example (live demo on: stackblitz):
// Works for MUI icons but might not for other libraries
const icons = { PersonIcon, ShoppingCartIcon, SecurityIcon, LocalActivityIcon };
const iconNames = Object.keys(icons);
export default function App() {
return (
<div className="App">
<h1>Icons</h1>
<ul>
{iconNames.map((icon, index) => {
const Icon = icons[icon];
return (
<li key={index}>
<Icon fontSize="large" color="primary" />
</li>
);
})}
</ul>
</div>
);
}
You are calling displayIcons as a variable while this is a function.
getIcons is a function not an array. You should do getIcons(icon)
Your code can be reduce to this
import { useCallback, useEffect, useMemo } from "react";
import PersonIcon from "#mui/icons-material/Person"
import ShoppingCartIcon from '#mui/icons-material/ShoppingCart'
import SecurityIcon from '#mui/icons-material/Security'
import LocalActivityIcon from '#mui/icons-material/LocalActivity'
export default function App() {
const iconNames = ['PersonIcon','ShoppingCartIcon', 'SecurityIcon', 'LocalActivityIcon']
const getIcons = (icon) => {
const icons = {
PersonIcon: <PersonIcon />,
ShoppingCartIcon: <ShoppingCartIcon />,
SecurityIcon: <SecurityIcon />,
LocalActivityIcon: <LocalActivityIcon />
}
return icons[icon];
}
const displayIcons = useCallback(() => {
return (
<ul>{iconNames.map((icon) => { return getIcons(icon) })}</ul>
)
},[iconNames])
return (
<div className="App">
<h1>Icons</h1>
{displayIcons()}
</div>
);
}
I'm following this tutorial on YouTube https://youtu.be/b9eMGE7QtTk
The full code can be found here: https://gist.github.com/adrianhajdin/997a8cdf94234e889fa47be89a4759f1
The tutorial was great, but it didn't split all the functionalities into components which is React used for (or I'm so lead to believe).
So we have the App.js
import React, { useState, useEffect } from "react";
import MovieCard from "./MovieCard";
import SearchIcon from "./search.svg";
import "./App.css";
const API_URL = "http://www.omdbapi.com?apikey=b6003d8a";
const App = () => {
const [searchTerm, setSearchTerm] = useState("");
const [movies, setMovies] = useState([]);
useEffect(() => {
searchMovies("Batman");
}, []);
const searchMovies = async (title) => {
const response = await fetch(`${API_URL}&s=${title}`);
const data = await response.json();
setMovies(data.Search);
};
return (
<div className="app">
<h1>MovieLand</h1>
<div className="search">
<input
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
placeholder="Search for movies"
/>
<img
src={SearchIcon}
alt="search"
onClick={() => searchMovies(searchTerm)}
/>
</div>
{movies?.length > 0 ? (
<div className="container">
{movies.map((movie) => (
<MovieCard movie={movie} />
))}
</div>
) : (
<div className="empty">
<h2>No movies found</h2>
</div>
)}
</div>
);
};
export default App;
MovieCards.jsx is as follows:
import React from 'react';
const MovieCard = ({ movie: { imdbID, Year, Poster, Title, Type } }) => {
return (
<div className="movie" key={imdbID}>
<div>
<p>{Year}</p>
</div>
<div>
<img src={Poster !== "N/A" ? Poster : "https://via.placeholder.com/400"} alt={Title} />
</div>
<div>
<span>{Type}</span>
<h3>{Title}</h3>
</div>
</div>
);
}
export default MovieCard;
The app works, but I want to move className="search" to be its own component like Search /.
The code I end up having in App.js is
//at the top of App.jx
import Search from "./Search"
// in const App
<Search prop={searchMovies}/>
And in the new Seach / component
import { useState } from "react";
import SearchIcon from './search.svg';
const Search = ( prop ) => {
const [searchTerm, setSearchTerm] = useState("");
return (
<div className="search">
<input
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
placeholder="Search"
/>
<img
src={SearchIcon}
alt="search"
onClick={() => prop(searchTerm)}
//props used to be searchMovies
/>
</div>
)
}
export default Search;
When typing something in the search field on the app and clicking on the search icon I get the following error:
prop is not a function
If my research has been correct, I need to use a constructor and super()
But it seems like the constructor needs to be called in a class Search instead of const Search as it breaks the code. Is that the case or is there a way to use the constructor in a function component, or is there something else completely that I should do?
Also, if there is a great tutorial you could recommend for super() I'd be really grateful.
Other thing that I want to do is to make a Results component or call it whatever that would have the {movies?.length > 0 ? ( part of the code, but I feel like that will be a different headache.
Basically what I want is to have:
const App = () => {
return (
<div className="app">
<h1>Movie Site</h1>
<Search />
<Results />
</div>
);
};
Or as shown in the picture
Hope all this makes sense. Also, I want to preface that I do not expect anyone to write the code for me, but if it helps me understand this it's appreciated. YT tutorials are appreciated as well.
Okay, after a push in the right direction from jonrsharpe and renaming the props into random things I figured it out.
As jonrsharpe said, my function is prop.prop, so if I wanted to call searchTerm in
onClick={() => prop(searchTerm)}
it should be
onClick={() => prop.prop(searchTerm)}
Now, that works, but looks silly. So renaming the first "prop" in prop.prop and the prop in const Search to searchOnClick leaves searchOnClick.prop(searchTerm) which still works. Great.
Then in App.js renaming prop in Search prop={searchMovies} to searchOnClick={searchMovies} needs to be followed by renaming searchOnClick.prop in Search.jsx to searchOnClick.searchOnClick.
Lastly, we want to destructure the props as jonrsharpe said.
const Search = ( searchOnClick ) => {
would become
const Search = ( {searchOnClick} ) => {
That allows us to remake searchOnClick.searchOnClick(searchTerm) to searchOnClick(searchTerm) only.
The whole point is that the prop calls the whole componentName variable=value but it doesn't take the value of the variable automatically so it needs to be called like prop.variable until destructured where it can be called as variable only.
Now that I figured this out it feels silly spending two days on this. Thanks to jonrsharpe again, and hope this helps to someone else in the future.
This question already has answers here:
Curly Brackets in Arrow Functions
(3 answers)
Closed 1 year ago.
I am new at React and I am trying to do a very simple Todo app but I already got stuck.
I initialized with create-react-app and somehow nothing is showing when I loop through the state and call a component passing data to it.
Here is the App.js:
import React, { useState } from 'react';
import Todo from './components/Todo';
function App() {
const [title, setTitle] = useState(['test1', 'test2', 'test3'])
return (
<div className="App">
{title.map(item => {
<div>
<Todo item={item} />
</div>
})}
</div>
);
}
export default App;
And this is the Todo.js:
import React from 'react';
const Todo = ({item}) => {
return <div>{item}</div>
}
export default Todo;
The Todo component is called from the right folder (I checked many times), the files are saved and React is not returning any error, it just displays nothing in the DOM. If I loop through the title and display its content from the app.js it displays everything as normal.
Also, a console.log from inside Todo component is not triggered meaning, somehow the component is not imported.
What am I doing wrong here?
It's not displaying because you are not returning anything from map
Try something like below:-
Return from map using return keyword
{title.map(item => {
return (
<div>
<div>{item}</div>
</div>
)
})}
OR implicitly return line below:-
{title.map(item => (
<div>
<div>{item}</div>
</div>
))}
try something like this:
import React, { useState } from 'react';
import Todo from './components/Todo';
function App() {
const [title, setTitle] = useState(['test1', 'test2', 'test3'])
return (
<div className="App">
{title.map(item => {
return(
<div>
<Todo item={item} />
</div>
);
})}
</div>
);
}
export default App;
or
import React, { useState } from 'react';
import Todo from './components/Todo';
function App() {
const [title, setTitle] = useState(['test1', 'test2', 'test3'])
return (
<div className="App">
{title.map(item =>(
<div>
<Todo item={item} />
</div>
))}
</div>
);
}
export default App;
The transition was made through react-router. I am trying to make the first project using Redux and I ran into the problem that my page is loading data that is not needed at the moment.
To be more specific, I have 2 classes in one form for selecting 1 of 3 components and index 1 is attached to each, 2 and 3. In the console you can see that when clicked, Redux picks up the data in the Store.
After selecting any component, the button for going to the next page becomes available. After switching to it, I would like to display the already entered data in the corner, but a default value has already been entered there. I came to the conclusion that the second page, which is not available until the user chose the option, it is rendered before the selection and is not updated after that, that's why the default hangs.
Maybe there is an option to prohibit rendering until the flag takes a different value or something like that?
The picture shows that in the last file the value 0 is immediately taken and does not change anymore.
my code
import React, { Component } from 'react'
import { Card,CardGroup,Button,Navbar,Nav } from 'react-bootstrap'
import A2 from '../assets/A2example.png'
import A3 from '../assets/A3example.png'
import A4 from '../assets/A4example.png'
import {BrowserRouter, Route, Switch, Link, } from 'react-router-dom'
import ZakazPortretaSecond from '../pages/ZakazPortretaSecond';
import store from '../redux/store'
import { connect } from 'react-redux'
class ZakazPortreta extends Component {
constructor(props){
super(props)
this.state = {
choosen: 4
}
}
portretSize(a){
this.setState({
choosen: a
})
store.dispatch({
type: 'SET_FORMAT_BY',
payload: a,
})
console.log(store.getState())
}
render() {
return (
<div className = "ZakazDiv">
<h2>Выберите желаемый размер портрета:A{this.state.choosen}</h2><br/>
<Navbar.Toggle aria-controls = "responsive-navbar-nav">
<Link to="/ZakazPortretaSecond" >Далее</Link>
</Navbar.Toggle>
<CardGroup>
<Card>
<Card.Body className = "cards" onClick={()=> this.portretSize(4)}>
<Card.Title>А4</Card.Title>
<Card.Subtitle className="mb-2 text-muted">210×297 мм</Card.Subtitle>
<Card.Text>
<img src = {A4} width = "370" height = "550"/>
</Card.Text>
</Card.Body>
</Card>
<Card >
<Card.Body className = "cards" onClick={()=> this.portretSize(3)}>
<Card.Title>А3</Card.Title>
<Card.Subtitle className="mb-2 text-muted">297 x 420 мм</Card.Subtitle>
<Card.Text>
<img src = {A3} width = "370" height = "550"/>
</Card.Text>
</Card.Body>
</Card>
<Card >
<Card.Body className = "cards" onClick={()=> this.portretSize(2)}>
<Card.Title>А2</Card.Title>
<Card.Subtitle className="mb-2 text-muted"> 420 x 594 мм</Card.Subtitle>
<Card.Text>
<img src = {A2} width = "370" height = "550"/>
</Card.Text>
</Card.Body>
</Card>
</CardGroup>
<BrowserRouter >
<div>
<Nav />
<Route path="/ZakazPortretaSecond" component={ZakazPortretaSecond} />
</div>
</BrowserRouter >
</div>
)
}
}
const mapStateToProps = (state) => {
console.log("Actual value",store.getState())
return{
format: state.format,
}
}
export default connect(mapStateToProps)(ZakazPortreta)
and second file
import React, { Component } from 'react'
import store from '../redux/store'
console.log("inside last file",store.getState())
class ZakazPortretaSecond extends Component {
render() {
return (
<div>
<h2 className='textSecond text-center'>Вы выбрали:A{store.format}</h2>
</div>
)
}
}
export default ZakazPortretaSecond
I solved the problem by updating the information separately on the page on load
Worked code:
import React, { Component } from 'react'
import store from '../redux/store'
console.log("inside last file",store.getState())
const refreshInf = () => {
let formats = store.getState().format
console.log(formats)
return formats
}
class ZakazPortretaSecond extends Component {
render() {
return (
<div onLoad = {refreshInf()}>
<h2 className='textSecond text-center'>Вы выбрали:A{refreshInf()}</h2>
</div>
)
}
}
export default ZakazPortretaSecond
function refreshInf on div has been refresh information in store!
so I've recently completed a project that I was working on that displayed a list of pokemon, and once clicked on, the user is directed to the pokemon information page.
It looks as follows:
So I have my main dashboard.js that contains my "PokemonList" as follows:
import React, { Component } from "react";
import styled from "styled-components";
import PokemonList from "../pokemon/PokemonList";
export default class Dashboard extends Component {
render() {
return (
<div>
<div className="row">
<div className="col">
<PokemonList />
</div>
</div>
</div>
);
}
}
my PokemonList.js is responsible for obtaining the Pokemon information from the PokeAPI and the code is as follows:
import React, { Component } from "react";
import PokemonCard from "./PokemonCard";
import axios from "axios";
export default class PokemonList extends Component {
state = {
url: "http://pokeapi.co/api/v2/pokemon/?limit=600",
pokemon: null
};
async componentDidMount() {
const res = await axios.get(this.state.url);
this.setState({ pokemon: res.data["results"] });
}
render() {
return (
<React.Fragment>
{this.state.pokemon ? (
<div className="row">
{this.state.pokemon.map(pokemon => (
<PokemonCard
key={pokemon.name}
name={pokemon.name}
url={pokemon.url}
/>
))}
</div>
) : (
<h1>Loading Pokemon</h1>
)}
</React.Fragment>
);
}
}
The pokemonList is built of several pokemonCards that's then displayed, but I don't think the coding for that is needed for what I'm looking for.
If I wanted to enable pagination, would I have to incorporate the code within my Dashboard.js or the pokemonList.js?
-----------------------EDIT--------------------------------------
What you could use is this library: https://www.npmjs.com/package/react-js-pagination
Then in your code the pagination would be smth like this:
<Pagination
activePage={this.state.activePage}
itemsCountPerPage={this.state.itemsCountPerPage}
totalItemsCount={this.state.pokemon.length}
pageRangeDisplayed={5}
onChange={::this.handlePageChange.bind(this)}
/>
handlePageChange function:
handlePageChange(pageNumber) {
this.setState({activePage: pageNumber});
}
then in the render function of your pokemonList.js:
let indexOfLastTodo = this.state.activePage * this.state.itemsCountPerPage;
let indexOfFirstTodo = indexOfLastTodo - this.state.itemsCountPerPage;
let renderedPokemons = this.state.pokemon.slice(indexOfFirstTodo, indexOfLastTodo);
and finally
{renderedPokemons.map(pokemon => (
<PokemonCard
key={pokemon.name}
name={pokemon.name}
url={pokemon.url}
/>
))}
Of course don't forget to include activePage and itemsCountPerPage in your state. I think I have done something like this in one of my earlier projects. Enjoy!