I'm using this documentation https://github.com/atlassian/react-beautiful-dnd to add drag and drop functionality to my app.
Working tutorial version: https://codepen.io/c15mnw/pen/wXyZrW?editors=0010
My rendition- https://stackblitz.com/edit/react-ef9gan
I've used most of the source code, but I've run into a stopping point since my app doesn't use fixed data like the example, I'm having trouble getting the list items to move into place.
My app uses conditional rendering to show append fields onto the current view based on whatever button click so there is conditional rendering in what should be in the drag and drop items.
I am at the point that I can drag individual input fields (clicking and holding the green area) but I can't drop them into place/change order.
Looking at my code, I believe that the problem is either in how I am mapping the data in return statement or in the way state was set in my application.
Tutorial code (omitted in my version):
const getItems = (count: number): Item[] =>
Array.from({ length: count }, (v, k) => k).map(k => ({
id: `item-${k}`,
content: `item ${k}`
}));
which is then passed into state...
this.state = {
items: getItems(6),
};
instead my state is passed like this in components/InputShow.jsx
this.state = {
items: [this.props.node]
};
this.onDragEnd = this.onDragEnd.bind(this);
}
I've taken a look at a few S/O answers including Conditionally render list with Higher Order Component & Conditional List in ReactJS Based On State among others but I didn't think the pertained to my situation.
I'm still pretty new to React, so I'm not sure exactly where I'm going wrong. Any help would be greatly appreciated
Related
To better understand the question open please these two sandboxes i made. One is written with react and the other with vanilla js.
React: https://codesandbox.io/s/boxes-v2-w400we (ignore any maximum call errors, idk what they want from me lol)
Vanilla: https://codesandbox.io/s/boxes-vanilla-js-z1z26l (refresh inside browser if boxes don't show up)
The problem is that react one is very slow. And not only state change, but even class toggle. I tried to separate these functions, so it at least look fast for user, even if state change need some more time behind the scenes, but it doesn't work somehow.
On the other hand vanilla one. Yes i'm mutating object there and it's not cool, but it works almost instantly even with 10k elements!
How to achieve same speed with react? And if this is not possible, then how to at least change color fast to hide slow state changing?
So, basically i need to change this function below (from react sandbox) to something that doesn't map through whole array, finding id, copying everything just to change one item. Since we know the id already and this id match index of this item in array, why can't we just change it directly like i did in vanilla version?
const updateData = id => {
setData(prevState => {
return prevState.map(el => {
return el.id === id ? { ...el, active: !el.active } : el;
});
});
};
I tried this, but it's still slow
const updateData = id => {
setData(prevState => {
const item = prevState[id];
const newState = [...prevState];
newState[id] = {...item, active: !item.active}
return newState;
})
}
I'm trying to build a Dashboard with React Js and I would like to know how you could display multiple components, but as widgets, which mean you need to be able to add them in any order and in any quantity. My problem is that I can't find a way to render a map of components.
The user may be able to add a widget to his dashboard by clicking a button, or remove them the same way, so I won't know how many of what components will be rendered
I would like to do something like, when the user clicks on a widget, it adds it to the dashboard, but I don't know how to do that.
I've tried to store components in a map, then with a forEach loop display them all by returning a div containing the component:
import Weather from '...'
import Currency from '...'
import News from '...'
const map = [Weather, Currency, News]
const runAll = () => {
map.forEach((fcn) => {
let runner = fcn
runner()
})
}
runAll()
I've searched many stack and other forums questions, without finding what I needed
Do you guys have an idea of what I could do to solve this ?
So you need to be able to easily render 2 things:
a list of widgets that the user can click and add in the dashboard
the actual dashboard. All selected widgets in a list (with a remove capability)
Let's first figure out what our state should be that also feeds the components 1 and 2.
For the 1st one we need the full list of available widgets. Since this is static (we have 3 widgets available) this can be expresses through a static mapping (a simple javascript object) declared once.
For the 2nd one we need an array of the user selected widgets. That's the dynamic part. We need to be able to set the initial widgets shown and have the capability to add and remove widgets from this list, allowing the same widget appearing more that once.
Static widget mapping
This should be a mapping between an identifier and the react widget component and should look like this:
import News from "./News";
import Weather from "./Weather";
import Currency from "./Currency";
const widgetsMapping = {
news: News,
weather: Weather,
currency: Currency
};
Widgets state
This is an array of widget identifiers (the keys from the static mapping) that the user wants in the dashboard. Also we need add and remove methods. Using useState we can write this like below:
const [widgets, setWidgets] = useState(["weather", "news"]);
const addWidget = (widget) => {
setWidgets([...widgets, widget]);
};
const removeWidget = (index) => {
const updated = [...widgets];
updated.splice(index, 1);
setWidgets(updated);
};
Rendering
Dashboard
Then we can render the dashboard by iterating our widget state array:
{widgets.map((widget, index) => {
const Widget = widgetsMapping[widget];
return (
<Widget
key={`${widget}${index}`}
removeWidget={() => removeWidget(index)}
/>
)
})}
removeWidget prop can be used to let a widget remove itself when sth is clicked.
List of available widgets
Here we will iterate through all available widgets from our static mapping and render all of them with the add functionality bound to them.
{Object.keys(widgetsMapping).map((widget) => (
<button key={widget} onClick={() => addWidget(widget)}>
{widget}+
</button>
))}
You can find a full working example in this code sandbox. Some assumptions were made about how you want to add and remove widgets but the main idea remains the same.
Keep a state (array) that holds widgets added by user. Define constants for widgets and save these constants to your persistance storage.
const widgets = {weather : 1, news: 2}
save these values to database as json with properties configured by user if needed, and then retrieve this json and render components based on it
sample JSON structure to save - [{type: 1, prop1: "val"},{type: 2, prop1: "val"}]
const renderWidgets = (array) => {
const widgets = [];
array.foreach((widget) => {
switch(widget) {
case widgets.weather:
widgets.push(<Weather ...props/>);
break;
.
.
.
etc
}
});
return widgets;
}
I have recreated my problem in a simplified code example.
Follow these steps to reproduce it:
Click on one of the three list items to edit the value in the off-canvas box.
Change the value and click the save button.
Select a different item to edit & save.
Note: the original edited item has reverted back to its initial state.
I have the console.logging in the save method to show that the list(state) is not the current (visible) version but the initial state.
Sandbox code example
I have an inelegant solution(workaround) that I will put as an answer but it doesn't explain what or why this is happening. I have 3 off-canvas editors like this on my page & one does work as expected but the other two loose state when calling their save functions in the parent.
Here is my bad (ugly) workaround:
Pass the state object (myList) to the child in props object.
App.js
function open(e, item, itemIndex) {
setPropArgs({ ...propArgs, editThis: item, index: itemIndex, show: true, itemList: myList });
}
Return the list back to the parent in save method call & use the passed variable instead of state.
MyEditor.js
<Button type="button" onClick={(e) => props.save(edit, props.index, props.itemList)}>
Save
</Button>
App.js
function save(edit, index, itemList) {
// console.log(myList);
const newList = [...itemList];
newList[index] = edit;
setMyList(newList);
setPropArgs({ ...propArgs, show: false });
}
This is bad because MyEditor shouldn't need to know anything about the parent, it doesn't read or edit the list at all & there shouldn't be a need to pass around a copy of the state that could become out of date (if I wasn't blocking with the canvas).
Okay... I found the answer that I was looking for.
Thanks to Marco Nisi for this Answer to a very similar question that has not got much attention
My forked code solution here
The solution is to move the callback (save function) into be created when the canvas is shown instead of when the functional component is initialized creating a fresher clone of the state. I can still see this as being problematic if you have an example where the state is being updated in the background or if the off-canvas is not blocking other edits. Note: You don't need the callback defined in the open function but it does need to be added(replaced) to the props object there.
function open(e, item, itemIndex) {
save = (edit, index) => {
console.log(myList);
const newList = [...myList];
newList[index] = edit;
setMyList(newList);
setPropArgs({ ...propArgs, show: false });
};
setPropArgs({
...propArgs,
editThis: item,
index: itemIndex,
show: true,
save: save
});
}
This is more of a hypothetical question as I would like to have a clear idea before trying to write the code for this problem.
As an example, lets say I have Board (parent) and Card (children) components.
The Cards are placed inside the Board and contain some text elements inside (maybe Todo items) and can be moved around inside the board and from board to board. Thus, state is used to allow users to move the cards from board to board, then "check-off" a todo item, and not reset everything to original positions.
But what happens if I want to persist after a user reloads the page?
Is my only option to store everything in localStorage after "stringifying", or are there other (better) alternatives?
I see a lot of examples online with a single component where you simply store the state and text of that component, but that seems very inefficient and complex when it comes to components with children.
The usual way is using localStorage. A full example of how localStorage works, you can change the states with your own.
const LOCAL_STORAGE_KEY = 'todosvar'
useEffect(() => {
const storedTodos = JSON.parse(localStorage.getItem
(LOCAL_STORAGE_KEY))
if (storedTodos) setTodos(storedTodos)
}, [])
useEffect(() => {
localStorage.setItem(LOCAL_STORAGE_KEY, JSON.stringify(todosvar))
}, [todosvar])
I am trying to implement a table in react where the user can edit individual rows by clicking the edit button on a row and then submit once he has made his change. I have say two components App.js and its child Table.js to implement this.
The way I thought of doing this initially was letting each of this component have their own state for rows and then the Table component reads from the props send to it by parent initially and only change the parent rows when users submits the change as oppose to onChange event. But I've read that reading props into state is an anti-pattern.
So decided to have everything in the parent by having two values for row (oldrows,newrows). And using them to maintain state instead, This is the design I came up with :
But what happens is whenever I click cancel the oldRows get bound to the newRows, here is a codePen example I put up:
https://codepen.io/snedden-gonsalves/pen/zYOVMWz
handleChangeRowInput = (event, keyValue) => {
let keyVals = [...this.state.newValuesArray];
keyVals[this.state.editIndex][keyValue] = event.currentTarget.value;
this.setState({
newValuesArray: keyVals
})
}
handleCancelRowInput = () => {
this.setState({
newValuesArray: [...this.state.oldValuesArray],
editIndex: -1
})
console.log('array', this.state.newValuesArray)
}
handleSubmitRowInput = () => {
this.setState({
oldValuesArray: [...this.state.newValuesArray],
editIndex: -1
})
}
In the codePen example if you enter a new value then cancel and then try adding a new value again the the old values and new values get bound.
I tried using lodash deepClone but it didn't work out, not sure why this is happening.
Also if you could comment on what is the best way to design this in react that would be awesome as I am very new to react and just trying to learn ..
I didn't find any issue after the cancel function. For me, the issue was coming up after I called the save function.
After clicking on the save button and then editing again, the old values and new values were get bound.
The handleSubmitRowInput function should create a new array for the oldValuesArray using the cloneDeep function
handleSubmitRowInput = () => {
this.setState({
oldValuesArray: _.cloneDeep(this.state.newValuesArray),
editIndex: -1
})
}