How to handle backend data for react-beautiful-dnd - reactjs

I have been working on a drag and drop planning feature, and I am attempting to pull saved data from my backend. I have been able to successfully log the needed data, however, when I am passing it into the react-beautiful-DnD template I have been using, the data fails to appear in the items array even though it is structured exactly the same as the static starter data in the other column.
const onDragEnd = (result, columns, setColumns) => {
if (!result.destination) return;
const { source, destination } = result;
if (source.droppableId !== destination.droppableId) {
const sourceColumn = columns[source.droppableId];
const destColumn = columns[destination.droppableId];
const sourceItems = [...sourceColumn.items];
const destItems = [...destColumn.items];
const [removed] = sourceItems.splice(source.index, 1);
destItems.splice(destination.index, 0, removed);
setColumns({
...columns,
[source.droppableId]: {
...sourceColumn,
items: sourceItems
},
[destination.droppableId]: {
...destColumn,
items: destItems
}
});
} else {
const column = columns[source.droppableId];
const copiedItems = [...column.items];
const [removed] = copiedItems.splice(source.index, 1);
copiedItems.splice(destination.index, 0, removed);
setColumns({
...columns,
[source.droppableId]: {
...column,
items: copiedItems
}
});
}
};
function DragTables() {
const itemStarter = [
{ id: uuid(), travel: "Flying from NYC to MCO", brand: "American Airlines", category: "Airline", Start: "8/12/21", End: "8/12/21", points: "10000", value: "500" }
];
useEffect (() => {
fetchNewData()
},[])
const [unplannedDataSet, setUnplannedDataSet] = useState([]);
async function fetchNewData() {
// const itineraryId = 2
const response = await fetch('http://localhost:5000/planner/getUnplannedItineraryData', {
method: "POST",
headers: {jwt_token: localStorage.token}
})
const dataSet = await response.json();
setUnplannedDataSet(dataSet)
}
useEffect (() => {
fetchPlannedData()
},[])
const [plannedDataSet, setPlannedDataSet] = useState([]);
async function fetchPlannedData() {
// const itineraryId = 2
const response = await fetch('http://localhost:5000/planner/getPlannedItineraryData', {
method: "POST",
headers: {jwt_token: localStorage.token}
})
const plannedDataSet = await response.json();
setPlannedDataSet(plannedDataSet)
}
const parsedData = [];
unplannedDataSet.forEach(element => {
parsedData.push({
id: element.id,
brand: element.brand
});
});
**const columnsFromBackend = {
//This does not
[uuid()]: {
name: "Itinerary",
items: plannedDataSet
},
//This works
[uuid()]: {
name: "Travel Options",
items: itemStarter
}
};**
const [columns, setColumns] = useState(columnsFromBackend);
//DND component
return (
<div>
<div style={{ display: "flex", justifyContent: "space-around", height: "100%", marginTop: 8}}>
<DragDropContext
onDragEnd={result => onDragEnd(result, columns, setColumns)}
>
{Object.entries(columns).map(([columnId, column], index) => {
return (
<div
style={{
display: "block",
flexDirection: "column",
alignItems: "center",
fontSize: 2
}}
key={columnId}
>
<h4 style={{ display: "flex",
justifyContent: "center",}}>{column.name}</h4>
<div style={{ display: "flex",
justifyContent: "center",
marginTop: 4}}>
<Droppable droppableId={columnId} key={columnId}>
{(provided, snapshot) => {
return (
<div
{...provided.droppableProps}
ref={provided.innerRef}
>
{column.items.map((item, index) => {
return (
<Draggable
key={item.id}
draggableId={item.id}
index={index}
>
{(provided, snapshot) => {
return (
<div className="snapshot"
ref={provided.innerRef}
{...provided.draggableProps}
{...provided.dragHandleProps}
>
<p style={{paddingLeft: 5, paddingTop: 1}}> <div style={{borderBottom: "1px solid white" }}><strong>{item.travel}</strong> </div>
<strong>Brand:</strong> {item.brand} | <strong># of Points:</strong> {item.points} | <strong>Point Value:</strong> ${item.value}
<br></br><strong>Category:</strong> {item.category} | <strong>Start Date:</strong> {item.Start} | <strong>End Date:</strong> {item.End}</p>
<p></p>
</div>
);
}}
</Draggable>
);
})}
{provided.placeholder}
</div>
);
}}
</Droppable>
</div>
</div>
);
})}
</DragDropContext>
</div>
</div>
);
}
export default DragTables;```

The reason why your code is not working is that you put const columnsFromBackend nested in your React DragTables Component. When you do this JavaScript will compile the code each time, producing a new copy of the columnsFromBackend Object, and React will not initialize the useEffect right, causing an infinite loop. Just put columnsFromBackend in the root scope and it will work right, but you will need to sync with the database.
You should not pass an object into useEffect, only a plain-old-data-type, but I think a string is okay but not an Object. I personally use a timer to autosave my state for my Chrome Extension. I have a useState number to switch tabs/views with my nav bar, and in each tab/view the timer updates a different part of my state relevant to the mode. You can also use a boolean useState and each time you change from true to false or false to true it saves.

Related

Show/Hide multiple elements of each button click with matching indexes in React JS

I have a scenario where I have 2 different components ( buttons and an info container). On each button click I am trying to display each matched info container. I am able to achieve the desired functionality in my buttons, but when I pass state back to my other component I am only able to display the matched index. My desired result is if I clicked a button in my nav and it has an active class all my "info container" should remain visible until the "active" class is toggled/removed.
JS:
...
const useStyles = makeStyles((theme) => ({
root: {
display: "flex",
"& > *": {
margin: theme.spacing(1)
}
},
orange: {
color: theme.palette.getContrastText(deepOrange[500]),
backgroundColor: deepOrange[500],
border: "4px solid black"
},
info: {
margin: "10px"
},
wrapper: {
display: "flex"
},
contentWrapper: {
display: "flex",
flexDirection: "column"
},
elWrapper: {
opacity: 0,
"&.active": {
opacity: 1
}
}
}));
const ToggleItem = ({ onChange, id, styles, discription }) => {
const [toggleThisButton, setToggleThisButton] = useState(false);
const handleClick = (index) => {
setToggleThisButton((prev) => !prev);
onChange(index);
};
return (
<>
<Avatar
className={toggleThisButton ? styles.orange : ""}
onClick={() => handleClick(id)}
>
{id}
</Avatar>
{JSON.stringify(toggleThisButton)}
{/* {toggleThisButton && <div className={styles.info}>{discription}</div> } */}
</>
);
};
const ToggleContainer = ({ discription, className }) => {
return <div className={className}> Content {discription}</div>;
};
export default function App() {
const data = ["first", "second", "third"];
const classes = useStyles();
const [value, setValue] = useState(false);
const handleChange = (newValue) => {
setValue(newValue);
console.log("newValue===", newValue);
};
return (
<>
<div className={classes.wrapper}>
{data.map((d, id) => {
return (
<div key={id}>
<ToggleItem
id={id}
styles={classes}
discription={d}
onChange={handleChange}
/>
</div>
);
})}
</div>
<div className={classes.contentWrapper}>
{data.map((d, id) => {
return (
<ToggleContainer
className={
value === id
? clsx(classes.elWrapper, "active")
: classes.elWrapper
}
key={id}
styles={classes}
discription="Hello"
/>
);
})}
</div>
</>
);
}
Codesanbox:
https://codesandbox.io/s/pedantic-dream-vnbgym?file=/src/App.js:0-2499
Codesandbox : https://codesandbox.io/s/72166087-zu4ev7?file=/src/App.js
You can store the selected tabs in a state. That way you don't need to render 3 (or more) <ToggleContainer>. In <ToggleContainer> pass the selected tabs as props and render the selected tabs content in <ToggleContainer>.
import React, { useState } from "react";
import "./styles.css";
import { makeStyles } from "#material-ui/core/styles";
import Avatar from "#material-ui/core/Avatar";
import { deepOrange } from "#material-ui/core/colors";
import clsx from "clsx";
const useStyles = makeStyles((theme) => ({
root: {
display: "flex",
"& > *": {
margin: theme.spacing(1)
}
},
orange: {
color: theme.palette.getContrastText(deepOrange[500]),
backgroundColor: deepOrange[500],
border: "4px solid black"
},
info: {
margin: "10px"
},
wrapper: {
display: "flex"
},
contentWrapper: {
display: "flex",
flexDirection: "column"
},
elWrapper: {
opacity: 0,
"&.active": {
opacity: 1
}
}
}));
const ToggleItem = ({ onChange, id, styles, discription }) => {
const [toggleThisButton, setToggleThisButton] = useState(false);
const handleClick = (index) => {
onChange(discription, !toggleThisButton);
setToggleThisButton((prev) => !prev);
};
return (
<>
<Avatar
className={toggleThisButton ? styles.orange : ""}
onClick={() => handleClick(id)}
>
{id}
</Avatar>
{JSON.stringify(toggleThisButton)}
{/* {toggleThisButton && <div className={styles.info}>{discription}</div> } */}
</>
);
};
const ToggleContainer = ({ className, selected }) => {
return (
<div className={className}>
{selected.map((item, idx) => (
<div key={idx}>Content {item}</div>
))}
</div>
);
};
export default function App() {
const data = ["first", "second", "third"];
const classes = useStyles();
const [selected, setSelected] = useState([]);
// action : False -> Remove, True -> Add
const handleChange = (val, action) => {
let newVal = [];
if (action) {
// If toggle on, add content in selected state
newVal = [...selected, val];
} else {
// If toggle off, then remove content from selected state
newVal = selected.filter((v) => v !== val);
}
console.log(newVal);
setSelected(newVal);
};
return (
<>
<div className={classes.wrapper}>
{data.map((d, id) => {
return (
<div key={id}>
<ToggleItem
id={id}
styles={classes}
discription={d}
onChange={handleChange}
/>
</div>
);
})}
</div>
<div className={classes.contentWrapper}>
<ToggleContainer styles={classes} selected={selected} />
</div>
</>
);
}

unable to handle multiple checks in Nested Table of Ant Design

check this code. when i check from one table it works properly.but when i check options from multiple tables it removes the data of other tables and only show current tables checked data,forEg :in my logic part when i select multiple checkbox from one table it uncheck all other tables
import axios from "axios";
import React, { useState, useEffect } from "react";
import permissions from "../Data/PermissionAPI.json";
import modules from "../Data/ModuleAPI.json";
import { Table, Row, Col, Form, Button, Checkbox } from "antd";
const TEsting = () => {
const [form] = Form.useForm();
const dData=[];
const [data, setData] = useState([]);
const [selectedRowKeys, setSelectedRowKeys] = useState([]);
const rowSelection = {
selectedRowKeys: selectedRowKeys,
onChange: (selectedRowKeys, selectedRows) => {
setSelectedRowKeys(selectedRowKeys);
console.log(`selectedRowKeys: ${selectedRowKeys}`, 'selectedRows: ', selectedRows);
},
};
const Permissions = ({ moduleid }) => {
const perm = permissions.filter((item) => item.moduleid === moduleid);
return (
<Table
style={{ width: "100%" }}
pagination={false}
rowSelection={rowSelection}
rowKey={(record) => record.id}
dataSource={perm}
columns={columns}
/>
);
};
const handleSubmit = (values) => {
console.log("Submit Pressed");
};
const columns = [
{
dataIndex: "key",
key: "key",
},
{
title: "Permission",
dataIndex: "pname",
key: "pname",
},
{
title: "Description",
dataIndex: "pdesc",
key: "pname",
},
];
const DisplayModules = modules.map((item, index) => {
const module = item;
// console.log(module);
if (module === undefined) return false;
return (
<Col xxl={12} xl={12} xs={24} key={index}>
{data}
<div
style={{
backgroundColor: "#ffe8c2",
padding: 20,
margin: "20px 20px 20px 20px",
borderRadius: "10px",
}}
title={`${module.id} - ${module.modulename}`}
>
<div className="about-project">
<Permissions moduleid={module.id} />
</div>
</div>
</Col>
);
});
useEffect(() => {
setData(dData);
console.log(data);
}, []);
return (
<div style={{ backgroundColor: "#e6e6e6" }}>
<Form
style={{ width: "100%" }}
form={form}
name="editSite"
onFinish={handleSubmit}
>
<Row gutter={25}>
{DisplayModules}
<div className="add-form-action">
<Form.Item>
<Button size="large" htmlType="submit" type="primary" raised>
Save
</Button>
</Form.Item>
</div>
</Row>
</Form>
</div>
);
};
export default TEsting;
here what i want:i want checkbox Data from multiple tables in one state without empty previous state,it would be very helpful if you help in this.
you need to decide - what data structure you want to get at the end, after user makes his choices and checks the checkboxes:
is that something like -
{
"moduleId": "1",
"permissions": [1,4],
"moduleId": "2",
"permissions": []
}
?
Try this solution
const [selectedRowKeys, setSelectedRowKeys] = useState([]);
const handleSelect = (record, selected) => {
if (selected) {
setSelectedRowKeys((ids) => [...ids, record.id]);
} else {
setSelectedRowKeys((ids) => {
const index = ids.indexOf(record.id);
return [...ids.slice(0, index), ...ids.slice(index + 1)];
});
}
};
const rowSelection = {
selectedRowKeys: selectedRowKeys,
onSelect: handleSelect,
};

Change only state of clicked card

How can I get the clicked card only to change its string from 'not captured' to 'captured'? Right now, all cards' strings say 'captured' even if I click on only one. I think the problem is that the captured state updates for all the cards and I can't get the captured state to update for the single clicked card. It's an onChange event and a checkbox.
import React, { useState, useEffect } from 'react'
import PokemonCard from '../components/PokemonCard';
const Pokedex = () => {
const [pokemons, setPokemons] = useState([]);
const [captured, setCaptured] = useState(false )
const URL = 'https://pokeapi.co/api/v2/pokemon/?limit=151';
const fetchingPokemons = async () => {
const res = await fetch(URL);
const data = await res.json();
// console.log(data)
setPokemons(data.results)
}
useEffect(() => {
fetchingPokemons()
}, [URL])
const toggleCaptured= (e, id) => {
console.log(id)
if(id && e) {
console.log('oh')
setCaptured(captured => !captured)
}
let capturedPkm = [];
let notCapturedPkm = [];
pokemons.forEach(i => {
if(captured === true) {
capturedPkm.push(pokemons[i])
} else {
notCapturedPkm.push(pokemons[i])
}
})
console.log('captured', capturedPkm, 'not captured', notCapturedPkm)
}
return (
<>
<div style={{display: 'flex', flexWrap: 'wrap', justifyContent: 'space-evenly'}}>
{pokemons ? pokemons.map((pokemon) => {
return (
<>
<div style={{ width: '235px' }} >
<PokemonCard
pokemon={pokemon}
name={pokemon.name}
url={pokemon.url}
key={pokemon.id}
captured={captured}
toggleCaptured={toggleCaptured}
/>
</div>
</>
)
}) : <h1>Loading...</h1>}
</div>
</>
)
}
export default Pokedex
import React, { useState, useEffect } from 'react';
import { Link } from 'react-router-dom';
import PokemonIcon from './PokemonIcon';
const PokemonCard = (props) => {
const { url, captured, toggleCaptured } = props
const URL = url
const [pokemonCard, setPokemonCard] = useState([])
const fetchingPokemonCard = async () => {
const res = await fetch(URL);
const data = await res.json();
//console.log(data)
setPokemonCard(data)
}
useEffect(() => {
fetchingPokemonCard()
}, [URL])
return (
<>
<div className='pokemon-card' style={{
height: '250px',
maxWidth: '250px',
margin: '1rem',
boxShadow: '5px 5px 5px 4px rgba(0, 0, 0, 0.3)',
cursor: 'pointer',
}} >
<Link
to={{ pathname: `/pokemon/${pokemonCard.id}` }}
state={{ pokemon: pokemonCard, captured }}
style={{ textDecoration: 'none', color: '#000000' }}>
<div
style={{ padding: '20px', display: 'flex', justifyContent: 'center', alignItems: 'center' }} >
<PokemonIcon img={pokemonCard.sprites?.['front_default']} />
</div>
</Link>
<div style={{ textAlign: 'center' }}>
<h1 >{pokemonCard.name}</h1>
<label >
<input
type='checkbox'
defaultChecked= {captured}
onChange={(e) => toggleCaptured(e.target.checked, pokemonCard.id)}
/>
<span style={{ marginLeft: 8, cursor: 'pointer' }}>
{captured === false ? 'Not captured!' : 'Captured!'}
{console.log(captured)}
</span>
</label>
</div>
</div>
<div>
</div>
</>
)
}
export default PokemonCard
Use an object as state:
const [captured, setCaptured] = useState({})
Set the toggle function:
const toggleCaptured = (checked, id) => {
const currentChecked = { ...captured }; // Create a shallow copy
console.log(id)
if (checked) {
currentChecked[id] = true;
} else {
delete currentChecked[id];
}
setCaptured(currentChecked); // Update the state
let capturedPkm = pokemons.filter(({id}) => currentChecked[id]);
let notCapturedPkm = pokemons.filter(({id}) => !currentChecked[id]);
console.log('captured', capturedPkm, 'not captured', notCapturedPkm)
}
The PokemonCard should look like this
<PokemonCard
pokemon={pokemon}
name={pokemon.name}
url={pokemon.url}
key={pokemon.id}
captured={captured[pokemon.id]} // Here
toggleCaptured={toggleCaptured}
/>

Data from the backend is not appearing when you assign it into new object on the front end

Question, I have this list of task coming from my backend, I dunno why my data is not appearing when I assign this to new object. I'm planning to create a drag and drop task component.
Datas
const BoardView = ({ datas, showTask, taskInfo }) => {
const [filteredList, setFilteredList] = useState(datas);
const listColumns = {
[uuidv4()]: {
name: 'Proposed',
items: filteredList,
},
[uuidv4()]: {
name: 'In Progress',
items: [],
},
[uuidv4()]: {
name: 'Review',
items: [],
},
[uuidv4()]: {
name: 'Done',
items: [],
},
};
console.log(datas) => data is appearing
return (
<>
<div className='w-full p-8 grid grid-flow-col grid-cols-4 gap-8'>
<DragDropContext
onDragEnd={(result) => onDragEnd(result, columns, setColumns)}
>
{Object.entries(columns).map(([columnId, column], index) => {
return (
<div
style={{
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
}}
key={columnId}
>
{console.log(column.item)} => there's no data
</div>
);
})}
</DragDropContext>
</div>
</>
);
};
Output
Your API endpoint is being hit when you are loading the component. Currently your component is loaded whilst the data is not yet fetched. So we'll fetch the data and then render the component
For this, wrap the logic for fetching the data in a useEffect hook - where you do 2 things -
Fetch the data
Populate the state with fetched data
Something like :
const BoardView = ({ datas, showTask, taskInfo }) => {
const [filteredList, setFilteredList] = useState(datas);
const listColumns = {
[uuidv4()]: {
name: 'Proposed',
items: filteredList,
},
[uuidv4()]: {
name: 'In Progress',
items: [],
},
[uuidv4()]: {
name: 'Review',
items: [],
},
[uuidv4()]: {
name: 'Done',
items: [],
},
};
useEffect(async () => {
// 1. => Fetching the data using "fetchAPI" or "axios". I have chosen axios as it is easier to work with
const results = await axios.get('API__ENDPOINT')
// 2. => Populate the state of this current component with fetched data
setFilteredList(results)
},[results])
console.log(datas) => data is appearing
return (
<>
<div className='w-full p-8 grid grid-flow-col grid-cols-4 gap-8'>
<DragDropContext
onDragEnd={(result) => onDragEnd(result, columns, setColumns)}
>
{Object.entries(columns).map(([columnId, column], index) => {
return (
<div
style={{
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
}}
key={columnId}
>
{console.log(column.item)} => there's no data
</div>
);
})}
</DragDropContext>
</div>
</>
);
};

useEffect gets stuck in an infinite loop

I am getting an infinite loop and I know the problem is because I am putting in brackets as the second argument the 'posts' and the 'setPost' inside the useEffect function, but I need the page to render whenever I add a new post, so the posts must be in brackets.
function Home() {
const {userData, setUserData} = useContext(userContext)
const [posts, setPost] = useState([])
const [createPost, setCreatePost] = useState('')
const handleToken = () => {
localStorage.removeItem('auth-token')
}
const token = localStorage.getItem("auth-token");
const handleOnSubmit = (e) => {
e.preventDefault()
axios.post('http://localhost:5000/posts', {textOfThePost: createPost}, {
headers: { 'auth-token': token },
})
.then((res) => {setCreatePost("")})
}
useEffect(() => {
axios.get('http://localhost:5000/posts')
.then(res => {
setPost(res.data)
})
}, [posts])
return (
<div className="home">
<div style={{display: 'flex', alignItems: 'center'}}>
<h1>this is the home: Welcome, {userData.username}</h1>
<Link style={{margin: 10}} to="/home">home</Link>
<Link style={{margin: 10}} to="/profile">profile</Link>
<Link style={{margin: 10}} onClick={handleToken} to="/">log out</Link>
</div>
<form onSubmit={handleOnSubmit}>
<input type="text" placeholder="What's happening?" value={createPost} onChange={e => setCreatePost(e.target.value)}/>
<button type="submit">tweet</button>
</form>
<div style={{display: 'flex', flexDirection: 'column'}}>
{posts.map(post => (
<div style={{border: '2px solid black', marginBottom: 10, marginRight: 'auto', marginLeft: 'auto', width: 300}} key={post._id}>
<div style={{display: 'flex', alignItems: 'center'}}>
<Avatar src={post.avatar}/>
<span style={{color: 'blue', marginLeft: 10}}>{post.name} <span style={{color: 'grey', fontSize: 11}}>#{post?.username}</span></span><br/>
</div>
<span>{post.textOfThePost}</span><br/>
<span>{moment(post.date).format('lll')}</span>
</div>
)).reverse()}
</div>
</div>
)
}
The problem here is the dependency array for useEffect (and similar hooks) doesn't use deep comparison (for performance reasons).
That is, whenever you get new data via Axios, res.data is a new JavaScript object and as you assign it to state, the effect dependency considers it an entirely changed object and runs the effect again, etc.
The easiest fix is to use a deep-comparing useEffect such as https://github.com/kentcdodds/use-deep-compare-effect .
You are not using posts in your effect, so it shouldn't be in your dependency array at all. One solution for your issue might be getting the posts in the first render, just once, then whenever you create a post, using the response updating the posts state.
const posts = [
{ id: "1", text: "foo" },
{ id: "2", text: "bar" },
{ id: "3", text: "baz" }
];
const API = {
getPosts: () =>
new Promise((resolve) => setTimeout(() => resolve(posts), 2000)),
createPost: () =>
new Promise((resolve) =>
setTimeout(() => resolve({ id: "3", text: "fizz" }), 1000)
)
};
function Posts() {
const [posts, setPosts] = React.useState([]);
React.useEffect(() => {
API.getPosts().then(setPosts);
}, []);
function handleOnSubmit() {
API.createPost().then((res) => setPosts((prev) => [...prev, res]));
}
return (
<div>
<div>
<button onClick={handleOnSubmit}>Create Post</button>
</div>
{!Boolean(posts.length) ? (
<span>Loading posts...</span>
) : (
posts.map((post) => <div>{post.text}</div>)
)}
</div>
);
}
ReactDOM.render(
<Posts />,
document.getElementById("root")
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.1/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.1/umd/react-dom.production.min.js"></script>
<div id="root" />
Don't bother the API part, I'm just mimicking your requests. The important part is after creating the post, using the response, and updating the state.

Resources