I am working on a project with React and Redux hooks. It has recursively nested kanban boards. When you add a new card to a kanban column that is scrolled all of the way to the right, the component re-renders and jerks the kanban board back to x=0, y-0 scroll. It is not the window that is scrolling. It is the kanban's parent element.
Does anyone know how you need to re-structure the List component (or any other part of the code), so that you can add a card to the columns (that are in a scrolled container), and not ruin the scroll position?
Thank you for any pointers!
I have tried using a ref to get the scroll position of the scrolled div right before the dispatch to add new card, and then set that position again as soon as the dispatch has added the card; but this did not work.
The data on the redux state is:
listSlice.js
list.nodes:
{
"0": {
"text": "board-one",
"id": 0,
"view": "board",
"childIds": [
100,
101,
102,
103,
104
],
"parentId": null,
"order": 0,
"isExpanded": true
},
"1": {
"text": "board-two",
"id": 1,
"view": "board",
"childIds": [
200,
201,
202,
203,
204
],
"parentId": null,
"order": 1,
"isExpanded": true
},
"2": {
"text": "board-three",
"id": 2,
"view": "board",
"childIds": [
300,
301,
302,
303,
304
],
"parentId": null,
"order": 2,
"isExpanded": true
},
"3": {
"text": "board-four",
"id": 3,
"view": "board",
"childIds": [
400,
401,
402,
403,
404
],
"parentId": null,
"order": 3,
"isExpanded": true
},
"100": {
"text": "backlog",
"id": 100,
"childIds": [
1000,
1001
],
"view": "column",
"order": 0,
"isExpanded": true
},
"101": {
"text": "to do",
"id": 101,
"childIds": [
2000,
2001
],
"view": "column",
"order": 1,
"isExpanded": true
},
"102": {
"text": "in progress",
"id": 102,
"childIds": [
3000,
3001
],
"view": "column",
"order": 2,
"isExpanded": true
},
"103": {
"text": "blocked",
"id": 103,
"childIds": [
4000,
4001
],
"view": "column",
"order": 3,
"isExpanded": true
},
"104": {
"text": "complete",
"id": 104,
"childIds": [
5000,
5001
],
"view": "column",
"order": 4,
"isExpanded": true
},
"200": {
"text": "backlog",
"id": 200,
"childIds": [
1000,
1001
],
"view": "column",
"order": 0,
"isExpanded": true
},
"201": {
"text": "to do",
"id": 201,
"childIds": [
2000,
2001
],
"view": "column",
"order": 1,
"isExpanded": true
},
"202": {
"text": "in progress",
"id": 202,
"childIds": [
3000,
3001
],
"view": "column",
"order": 2,
"isExpanded": true
},
"203": {
"text": "blocked",
"id": 203,
"childIds": [
4000,
4001
],
"view": "column",
"order": 3,
"isExpanded": true
},
"204": {
"text": "complete",
"id": 204,
"childIds": [
5000,
5001
],
"view": "column",
"order": 4,
"isExpanded": true
},
"300": {
"text": "backlog",
"id": 300,
"childIds": [
1000,
1001
],
"view": "column",
"order": 0,
"isExpanded": true
},
"301": {
"text": "to do",
"id": 301,
"childIds": [
2000,
2001
],
"view": "column",
"order": 1,
"isExpanded": true
},
"302": {
"text": "in progress",
"id": 302,
"childIds": [
3000,
3001
],
"view": "column",
"order": 2,
"isExpanded": true
},
"303": {
"text": "blocked",
"id": 303,
"childIds": [
4000,
4001
],
"view": "column",
"order": 3,
"isExpanded": true
},
"304": {
"text": "complete",
"id": 304,
"childIds": [
5000,
5001
],
"view": "column",
"order": 4,
"isExpanded": true
},
"400": {
"text": "backlog",
"id": 400,
"childIds": [
1000,
1001
],
"view": "column",
"order": 0,
"isExpanded": true
},
"401": {
"text": "to do",
"id": 401,
"childIds": [
2000,
2001
],
"view": "column",
"order": 1,
"isExpanded": true
},
"402": {
"text": "in progress",
"id": 402,
"childIds": [
3000,
3001
],
"view": "column",
"order": 2,
"isExpanded": true
},
"403": {
"text": "blocked",
"id": 403,
"childIds": [
4000,
4001
],
"view": "column",
"order": 3,
"isExpanded": true
},
"404": {
"text": "complete",
"id": 404,
"childIds": [
5000,
5001
],
"view": "column",
"order": 4,
"isExpanded": true
},
"1000": {
"text": "card-one",
"id": 1000,
"childIds": [],
"order": 0,
"isExpanded": true,
"view": "card"
},
"1001": {
"text": "card-two",
"id": 1001,
"childIds": [],
"order": 1,
"isExpanded": true,
"view": "card"
},
"2000": {
"text": "card-one",
"id": 2000,
"childIds": [],
"order": 0,
"isExpanded": true,
"view": "card"
},
"2001": {
"text": "card-two",
"id": 2001,
"childIds": [],
"order": 1,
"isExpanded": true,
"view": "card"
},
"3000": {
"text": "card-one",
"id": 3000,
"childIds": [],
"order": 0,
"isExpanded": true,
"view": "card"
},
"3001": {
"text": "card-two",
"id": 3001,
"childIds": [],
"order": 1,
"isExpanded": true,
"view": "card"
},
"4000": {
"text": "card-one",
"id": 4000,
"childIds": [],
"order": 0,
"isExpanded": true,
"view": "card"
},
"4001": {
"text": "card-two",
"id": 4001,
"childIds": [],
"order": 1,
"isExpanded": true,
"view": "card"
},
"5000": {
"text": "card-one",
"id": 5000,
"childIds": [],
"order": 0,
"isExpanded": true,
"view": "card"
},
"5001": {
"text": "card-two",
"id": 5001,
"childIds": [],
"order": 1,
"isExpanded": true,
"view": "card"
}
}
listSlice.js
list.visibleIds:
[
"0",
"1",
"2",
"3"
]
The component:
(to keep the example simple, there is only an add button on the column. The error occurs when the board is scrolled, to see a column at the end; and you try to add a card to one of those columns at the end).
List.js
import React, { useEffect } from "react";
import { useSelector, useDispatch } from "react-redux";
import { v4 as uuid } from "uuid";
import {
fetchNodesAsync,
selectVisibleIds,
selectParentId,
addNewNodeAsync,
selectNodes
} from "./listSlice";
import "./List.css";
export function List(props = {}) {
let { isRoot } = props;
const nodes = useSelector(selectNodes);
const visibleIds = useSelector(selectVisibleIds);
const parentId = useSelector(selectParentId);
const dispatch = useDispatch();
useEffect(() => {
if (isRoot) {
dispatch(fetchNodesAsync());
}
}, []);
const renderList = (params) => {
let { ids, parentView } = params;
return (
// This is the element that is scrolled when the error occurs. It is scrolled to the left, so that you can see the last columns in the list:
<ul className={`${parentView || "root"}-container`}>
{ids.map((id) => {
let node = nodes[id];
return (
<li key={uuid()} className={node.view}>
<div className="header">
{node.view === "column" && (
<button
onClick={(e) => {
e.preventDefault();
dispatch(addNewNodeAsync({ parentId: id }));
}}
>
+ add item
</button>
)}
id: {node.id} {node.text}
</div>
<div className={`${node.view}-scroll`}>
{node.isExpanded &&
node.childIds.length >= 1 &&
renderList({
ids: node.childIds,
parentId: id,
parentView: node.view
})}
</div>
</li>
);
})}
</ul>
);
};
return <ul className="List">{renderList({ ids: visibleIds, parentId })}</ul>;
}
I'm trying to implement the data-structure that Dan Abramov describes here:
https://github.com/reduxjs/redux/issues/1629
You cannot do this: <li key={uuid()} ... /> because uuid will generate a new random ID at every render, which will make React generate new DOM nodes at every state change and therefore will lose the scroll amount stored inside the previous nodes. You should use <li key={id} ... /> instead, so that the IDs stay the same.
I have a json which comes from an API,
"nutrient_value": [
{
"Calcium": [
"29.16",
"mg",
"Red",
"0.00102858984",
"ounce"
]
},
{
"Choline": [
"118.97",
"mg",
"Red",
"0.00419654778",
"ounce"
]
},
{
"Copper": [
"0.12",
"mg",
"Red",
"0.00000423288",
"ounce"
]
},
{
"Crude fat": [
"29.16",
"g",
"Green",
"1.02858984",
"ounce"
]
},
{
"Folate": [
"11.66",
"mcg",
"Red",
"0.00000041129484",
"ounce"
]
},
{
"Iodine": [
"0.0",
"mcg",
"Red",
"0.0",
"ounce"
]
},
{
"Iron": [
"4.08",
"mg",
"Yellow",
"0.00014391792",
"ounce"
]
},
{
"Magnesium": [
"34.99",
"mg",
"Green",
"0.00123423726",
"ounce"
]
},
{
"Manganese": [
"0.02",
"mg",
"Red",
"0.00000070548",
"ounce"
]
},
{
"Niacin (B3)": [
"9.04",
"mg",
"Green",
"0.00031887696",
"ounce"
]
},
{
"Omega-3 excl. ALA and SDA": [
"0.02",
"g",
"Red",
"0.00070548",
"ounce"
]
},
{
"Omega-6": [
"0.02",
"g",
"Red",
"0.00070548",
"ounce"
]
},
{
"Pantothenic acid (B5)": [
"1.07",
"mg",
"Red",
"0.00003774318",
"ounce"
]
},
{
"Phosphorus": [
"332.42",
"mg",
"Red",
"0.01172578308",
"ounce"
]
},
{
"Potassium": [
"573.48",
"mg",
"Yellow",
"0.02022893352",
"ounce"
]
},
{
"Protein": [
"36.94",
"g",
"Green",
"1.30302156",
"ounce"
]
},
{
"Riboflavin (B2)": [
"0.29",
"mg",
"Red",
"0.00001022946",
"ounce"
]
},
{
"Selenium": [
"30.72",
"mcg",
"Red",
"0.00000108361728",
"ounce"
]
},
{
"Sodium (Na)": [
"128.3",
"mg",
"Green",
"0.0045256542",
"ounce"
]
},
{
"Thiamin (B1)": [
"0.08",
"mg",
"Red",
"0.00000282192",
"ounce"
]
},
{
"Vitamin A": [
"8.16",
"mcg",
"Red",
"0.00000028783584",
"ounce"
]
},
{
"Vitamin C": [
"0.0",
"mg",
"Green",
"0.0",
"ounce"
]
},
{
"Vitamin D": [
"0.19",
"mcg",
"Red",
"0.00000000670206",
"ounce"
]
},
{
"Vitamin E": [
"0.33",
"mg",
"Red",
"0.00001164042",
"ounce"
]
},
{
"Zinc (Zn)": [
"8.71",
"mg",
"Red",
"0.00030723654",
"ounce"
]
},
{
"Calories": [
"417.96",
"cal",
null,
"N/A",
"ounce"
]
},
{
"Omega-3/6 ratio": [
"0.65",
"ratio",
"Green",
"N/A",
"ounce"
]
},
{
"Calcium/Phosphorus ratio": [
"0.06",
"ratio",
"Red",
"N/A",
"ounce"
]
}
],
From the above json im decoding it into this
var resJson = json.decode(res);
MPD.Result _mealPlan = MPD.Result.fromJson(resJson["result"]);
///
factory Result.fromJson(Map<String, dynamic> json) => Result(
id: json["id"],
....
nutrientValue: List<NutrientValue>.from(json["nutrient_value"].map((x) => NutrientValue.fromJson(x))),
....
);
"nutrient_value": []
And inside this nutrient_value array, for each item I need to check if given key (eg: "Calcium") exists and if yes, then fetch the values of index [0] and [2] of that key:
Example key "Calcium" exists and
valueCalcium = value of index[0]
colorCalcium = value of index[2]
"nutrient_value": [
{
"Calcium": [
"29.16",
"mg",
"Red",
"0.00102858984",
"ounce"
}
how can I do this in an iterative way?
I have tried this
_mealPlan.nutrientValue.forEach((element) {
if(element.calcium.isNotEmpty){
valCalcium = element.calcium[0];
print(valCalcium);//_mealPlan.nutrientValue[0].calcium[0];
calciumClr = element.calcium[2];
print(calciumClr);
}
});
but it doesn't seem to be the right way.
I'm using each of those two index values in later parts in my code.
You can do like this:
void main() {
Map<String, dynamic> data = {
"success": true,
"result": {
"id": 7,
"daily_allowance_actual_percentage": {
"Bone": [0, "Red"],
"Muscle Meat": [100, "Red"]
},
"daily_calories_from_this_meal_plan": "417.96",
"imperial_weight_of_one_meal": "2.2857552",
"meal_ingredients_would_make": 3,
"meals_per_day": 3,
"nutrient_value": [
{
"Calcium": ["29.16", "mg", "Red", "0.00102858984", "ounce"]
},
{
"Choline": ["118.97", "mg", "Red", "0.00419654778", "ounce"]
},
{
"Copper": ["0.12", "mg", "Red", "0.00000423288", "ounce"]
},
{
"Crude fat": ["29.16", "g", "Green", "1.02858984", "ounce"]
},
{
"Folate": ["11.66", "mcg", "Red", "0.00000041129484", "ounce"]
},
{
"Iodine": ["0.0", "mcg", "Red", "0.0", "ounce"]
},
{
"Iron": ["4.08", "mg", "Yellow", "0.00014391792", "ounce"]
},
{
"Magnesium": ["34.99", "mg", "Green", "0.00123423726", "ounce"]
},
{
"Manganese": ["0.02", "mg", "Red", "0.00000070548", "ounce"]
},
{
"Niacin (B3)": ["9.04", "mg", "Green", "0.00031887696", "ounce"]
},
{
"Omega-3 excl. ALA and SDA": [
"0.02",
"g",
"Red",
"0.00070548",
"ounce"
]
},
{
"Omega-6": ["0.02", "g", "Red", "0.00070548", "ounce"]
},
{
"Pantothenic acid (B5)": [
"1.07",
"mg",
"Red",
"0.00003774318",
"ounce"
]
},
{
"Phosphorus": ["332.42", "mg", "Red", "0.01172578308", "ounce"]
},
{
"Potassium": ["573.48", "mg", "Yellow", "0.02022893352", "ounce"]
},
{
"Protein": ["36.94", "g", "Green", "1.30302156", "ounce"]
},
{
"Riboflavin (B2)": ["0.29", "mg", "Red", "0.00001022946", "ounce"]
},
{
"Selenium": ["30.72", "mcg", "Red", "0.00000108361728", "ounce"]
},
{
"Sodium (Na)": ["128.3", "mg", "Green", "0.0045256542", "ounce"]
},
{
"Thiamin (B1)": ["0.08", "mg", "Red", "0.00000282192", "ounce"]
},
{
"Vitamin A": ["8.16", "mcg", "Red", "0.00000028783584", "ounce"]
},
{
"Vitamin C": ["0.0", "mg", "Green", "0.0", "ounce"]
},
{
"Vitamin D": ["0.19", "mcg", "Red", "0.00000000670206", "ounce"]
},
{
"Vitamin E": ["0.33", "mg", "Red", "0.00001164042", "ounce"]
},
{
"Zinc (Zn)": ["8.71", "mg", "Red", "0.00030723654", "ounce"]
},
{
"Calories": ["417.96", "cal", null, "N/A", "ounce"]
},
{
"Omega-3/6 ratio": ["0.65", "ratio", "Green", "N/A", "ounce"]
},
{
"Calcium/Phosphorus ratio": ["0.06", "ratio", "Red", "N/A", "ounce"]
}
],
"pet_name": "tim",
"show_weight_of_balanced_meal": true,
"suggestion": {
"add_food": {
"Red": [
{
"Calcium": ["Amaranth, grain, whole, uncooked", "Apple, dried"]
},
{
"Copper": ["Amaranth, grain, whole, uncooked", "Apple, dried"]
},
{
"Folate": [
"Amaranth, grain, whole, uncooked",
"Apple, fuji, unpeeled, raw"
]
},
{
"Iodine": ["Amaranth, grain, whole, uncooked", "Apricot, dried"]
},
{
"Manganese": ["Amaranth, grain, whole, uncooked", "Apple, dried"]
},
{
"Omega-3 excl. ALA and SDA": ["Bass, fillet, raw", "Beef Brain"]
},
{
"Omega-6": ["Amaranth, grain, whole, uncooked", "Avocado, raw"]
},
{
"Pantothenic acid (B5)": [
"Amaranth, grain, whole, uncooked",
"Apple, dried"
]
},
{
"Riboflavin (B2)": [
"Amaranth, grain, whole, uncooked",
"Apple, fuji, unpeeled, raw"
]
},
{
"Thiamin (B1)": [
"Amaranth, grain, whole, uncooked",
"Apple, fuji, unpeeled, raw"
]
},
{
"Vitamin A": ["Bean, edamame, from frozen, cooked", "Beef Brain"]
},
{
"Vitamin D": ["Beef kidney, raw", "Beef liver, raw"]
},
{
"Vitamin E": ["Amaranth, grain, whole, uncooked", "Apple, dried"]
},
{
"Crude fat": ["Amaranth, grain, whole, uncooked", "Apple, dried"]
},
{
"Magnesium": ["Amaranth, grain, whole, uncooked", "Apple, dried"]
},
{
"Niacin (B3)": [
"Apple, fuji, unpeeled, raw",
"Apple, golden delicious, unpeeled, raw"
]
},
{
"Protein": ["Amaranth, grain, whole, uncooked", "Apple, dried"]
},
{
"Sodium (Na)": [
"Amaranth, grain, whole, uncooked",
"Apple, dried"
]
}
],
"Yellow": []
},
"remove_food": {
"Red": [
{
"Choline": ["Beef, mince, 15% fat, raw"]
},
{
"Phosphorus": ["Beef, mince, 15% fat, raw"]
},
{
"Selenium": ["Beef, mince, 15% fat, raw"]
},
{
"Zinc (Zn)": ["Beef, mince, 15% fat, raw"]
}
],
"Yellow": [
{
"Iron": ["Beef, mince, 15% fat, raw"]
},
{
"Potassium": ["Beef, mince, 15% fat, raw"]
}
]
}
},
"total_calories_in_a_meal_plan": 645,
"total_imperial_weight_of_a_meal_plan": 10.5822,
"total_weight_of_a_meal_plan": 300,
"weight_of_one_meal": "64.8",
"feeding_plan_details": [
{
"id": 23,
"imperial_quantity": 10.5822,
"quantity": 300,
"food": {
"id": 6,
"bone_percentage": 0,
"name": "Beef, mince, 15% fat, raw"
}
}
]
}
};
List<Map<String, dynamic>> nutrientValue = data["result"]["nutrient_value"];
List<Map<String, dynamic>> filteredData = nutrientValue
.where((Map<String, dynamic> element) =>
element.keys.toList()[0] == "Calcium")
.toList();
for (Map<String, dynamic> i in filteredData) {
print("${i[i.keys.toList()[0]][0]} && ${i[i.keys.toList()[0]][2]}");
}
}
I have two charts divs in html which I am able to display first time. but when my state changes and I come back to the state where I displayed charts. Then charts are not shown. I think I have to re-attach the dataProvider and render it again but i don't know how.
this is my controller
.controller('HomeCtrl', function ($location,$window) {
AmCharts.makeChart("chartdiv-pie", {
"type": "pie",
"theme": "light",
"dataProvider": [{
"title": "20%",
"value": 3852,
"color": "#18aa9f"
}, {
"title": "40%",
"value": 3899,
"color": "#e65548"
},
{
"title": "40%",
"value": 4899,
"color": "#e1e3e4"
}
],
"title": "AmCharts",
"color": "black",
"titleField": "title",
"valueField": "value",
"radius": "25%",
"outlineAlpha":0,
"innerRadius": "40%",
"balloonText": "[[value]]",
"labelText": "[[title]]",
"labelsEnabled": true,
"colorField": "color",
"labelText": "[[title]]",
"export": {
"enabled": true
},
});
var chart = AmCharts.makeChart( "chartdiv-male", {
"type": "serial",
"addClassNames": true,
"theme": "light",
"autoMargins": false,
"marginLeft": 30,
"marginRight": 8,
"marginTop": 10,
"marginBottom": 26,
"balloon": {
"adjustBorderColor": false,
"horizontalPadding": 10,
"verticalPadding": 8,
"color": "#ffffff"
},
"dataProvider": [ {
"year": "Pos",
"income": 30.1,
"color": "#FF0F00",
"expenses": 34.9,
}, {
"year": "Neg",
"income": 29.5,
"expenses": 31.1
}, {
"year": "Neu",
"income": 30.6,
"expenses": 28.2,
"dashLengthLine": 5,
"columnColor": 'green'
} ],
"valueAxes": [ {
"axisAlpha": 0,
"position": "left"
} ],
"startDuration": 1,
"graphs": [ {
"alphaField": "alpha",
"balloonText": "<span style='font-size:12px;'>[[title]] in [[category]]:<br><span style='font-size:20px;'>[[value]]</span> [[additional]]</span>",
"fillAlphas": 1,
"title": "Income",
"type": "column",
"valueField": "income",
"dashLengthField": "dashLengthColumn"
}, ],
"categoryField": "year",
"categoryAxis": {
"gridPosition": "start",
"axisAlpha": 0,
"tickLength": 0
},
"export": {
"enabled": true
}
} );
}
these are my states
.state('home.stats', {
url:'/stats',
templateUrl: 'views/stats.html',
controller: 'StatsCtrl',
controllerAs: 'stats'
})
.state('home.posts', {
url:'/posts',
templateUrl: 'views/posts.html',
controller: 'PostsCtrl',
controllerAs: 'posts'
});