How to create a react native state with key and value array? - javascript

I've a flatlist with a list of options and a checkboxes in each row, and I need a state for the checkboxex, using hooks. I tought create a key-value relationship, an associative array somehow, so I can access to the proper state using the "item.option" as key:
export default function Questions({ navigation, route }) {
const [questionNumber, setQuestionNumber] = useState(0);
const data = route.params.data;
const numberOfQuestions = Object.keys(data.questions).length;
const [selectedOption, setSelectedOption] = useState([null]);
const [toggleCheckBox, setToggleCheckBox] = useState([false])
[...]
const renderItemMultipleChoice = ({ item }) => {
console.log(toggleCheckBox[item.option],item.option); //******here I get undefined
return (
<View style={[styles.option]}>
<CheckBox style={styles.checkBox}
disabled={false}
value={toggleCheckBox[item.option]}
onValueChange={(newValue) => multipleChoiceHandler(item.option, newValue)}
/>
<Text style={styles.optionText}>{item.option}</Text>
</View>
);
};
const multipleChoiceHandler = (item, newValue) => {
var newHash = toggleCheckBox
newHash[item] = newValue;
setToggleCheckBox({toggleCheckBox: newHash});
}
useEffect(() => {
if (data.questions[questionNumber].type_option != "open_choice") {
for (i = 0; i < Object.keys(data.questions[questionNumber].options).length; i++) {
var newHash = toggleCheckBox
newHash[data.questions[questionNumber].options[i].option] = false;
//*******every checkbox is false at the beginning...
setToggleCheckBox({toggleCheckBox: newHash});
console.log("toggle checkbox:",toggleCheckBox[data.questions[questionNumber].options[i].option],
data.questions[questionNumber].options[i].option); //****** here I get all false, the value I setted.
}
setSelectedOption([null]);
}
}, [questionNumber])
return(
<FlatList style={styles.flatlistOption}
data={data.questions[questionNumber].options}
renderItem={renderItemMultipleChoice}
keyExtractor={(item) => item.option}
/>
)
}
I'm supernoob about react, so to insert the intial state of toggleCheckBox for each element (using the parameter option to refer to the proper array element), I've used a for cycle... I know it's not proper and quite spaghetti code. Btw it should work, but when I try to access from the checklist to the toggleCheckBox state I get a undefined, so the checkbox state doesn't work properly. I don't know what I'm missing...

Related

React: Warning: Each child in a list should have a unique "key" prop

I'm experiencing this warning with different components in my application but I will take just the following one as example.
I have got the following component. It has a map function that renders multiple components from an array:
const Clipboards = () => {
const { user, addClipboard } = useAuth();
const clipboards = user?.clipboards || [];
return (
<>
{clipboards.map(clipboard =>
<Clipboard clipboard={clipboard}/>
)}
<IconButton onClick={() => addClipboard()} color={'secondary'}>
<PlusIcon/>
</IconButton>
</>
)
};
export default Clipboards;
with Clipboard being as follows. As you can see, I use key prop in the wrapping div:
const Clipboard = ({clipboard, setSelectedClipboard}) => {
const {addClipboardRubric} = useAuth();
const {enqueueSnackbar} = useSnackbar();
const selectClip = () => {
setSelectedClipboard({id: clipboard.id, name: clipboard.name})
};
const [{isActive}, drop] = useDrop(() => ({
accept: 'rubric',
collect: (monitor) => ({
isActive: monitor.canDrop() && monitor.isOver(),
}),
drop(item, monitor) {
handleDrop(item)
},
}));
const handleDrop = async (rubric) => {
try {
await addClipboardRubric({
clipboardId: clipboard.id,
rubric: rubric
})
enqueueSnackbar('Rubric added', {
variant: 'success'
});
} catch (e) {
enqueueSnackbar('Rubric already added', {
variant: 'error'
});
}
}
console.log(`clip-${clipboard.id}`)
return (
<div ref={drop} key={clipboard.id}>
<IconButton
id={`clip-${clipboard.id}`}
onClick={selectClip}
color={'secondary'}
>
<Badge badgeContent={clipboard?.rubrics?.length || 0} color={"secondary"}>
<ClipboardIcon/>
</Badge>
</IconButton>
</div>
)
}
export default connect(
({search: {repertories = {}}}) => ({repertories}),
(dispatch) => ({
setSelectedClipboard: (payload) => dispatch(SET_SELECTED_CLIPBOARD(payload)),
})
)(Clipboard);
As you can see. I'm adding the key and it is unique.
What would be the problem then?
You have to add a unique key to every item returned from the map function. For example, you can use the index as a unique key which is not recommended but just to give you an example. In your code, you are adding a key inside the component. You need to add the key to the component itself. Check the below code.
const Clipboards = () => {
const { user, addClipboard } = useAuth();
const clipboards = user?.clipboards || [];
return (
<>
{clipboards.map((clipboard,index) =>
<Clipboard clipboard={clipboard} key={index}/>
)}
<IconButton onClick={() => addClipboard()} color={'secondary'}>
<PlusIcon/>
</IconButton>
</>
)
};
export default Clipboards;
You haven't added a key to the Clipboard component which is returned by the map.
Keys help React identify which items have changed, are added, or are removed. Keys should be given to the elements inside the array to give the elements a stable identity.
Source
Try something like this (if Clipboard as an id property):
{clipboards.map(clipboard =>
<Clipboard key={clipboard.id} clipboard={clipboard}/>
)}
Read this before using the index as a key

Deep copy data update problem in react native

I am using this
const HomeProfile = ({data}) =>{
var tempData = {}
const onOptionClick = ( clicked_index) => {
tempData = { ...data[0].atpData }
tempData.selected_index = clicked_index
}
const addInProps = () =>{
data[0].atpData=tempData
}
const checkData = () =>{
console.log('tempData',tempData)
}
return(
<View>
<Text></Text>
</View>
)
}
When check data in checkData function it is giving me the empty data. But if i am using this hooks like
const [tempData,settempData] = usestate({})
My Problem:
It is working good as i am using it with var tempData = {} but problem in that after adding data. Still getting empty Data in the checkData() functions
If i am using with const [tempData,settempData] = usestate({}) then it is changing my data in the data[0].atpData without hitting the function so i am not getting how to resolve it. But i am getting data checkData() function as i required.
You should use useState hook, so you need initialize it as empty object, and call setter inside your method as you desire:
const HomeProfile = ({data}) =>{
const [tempData, setTempData] = useState({})
const onOptionClick = ( clicked_index) => {
setTempData({...data[0].atpData, selected_index: clicked_index})
}
const addInProps = () =>{
data[0].atpData=tempData
}
const checkData = () =>{
console.log('tempData',tempData)
}
return(
<View>
<Text></Text>
</View>
)
}
you can use useEffect hook when detect the object change

Rerender Flatlist after Call api then setState

I just started learning React native, and want to render FlaList after setState.
I am try to call Api to get Data and then I sorting that data but the FlatList is not rerender with newData. I also try extraData but nothing happen. Where am I missing?
Thank for your help.
function HomeScreen(props) {
const {transactions = []} = useSelector(selectors.transactions) || [];
const [listTransaction, setListTransaction] = useState([]);
useEffect(() => {
dispatch(BalanceActions.balanceRequest()); // This is my call Api
sortListTransaction(); // I call sortFunc after that
}, []);
const sortListTransaction = () => { // In this function I group by date the array of the result Api
let groups = [];
transaction.forEach((item) => {
let date = moment(item.date).format('MM/DD/YYYY');
if (date in groups) {
groups[date].push(item);
} else {
groups[date] = new Array(item);
}
});
setListTransaction(groups);
};
const _renderItem = ({item}) => {
return <BodyContent data={item} />;
};
// Then my FlatList like:
return (
<FlatList
data={listTransaction}
keyExtractor={(item) => item.id}
renderItem={_renderItem}
extraData={listTransaction}
/>
)
}

React useState setter wont update the value inside a context

So, I'm using the React Context API to manage some global state for my app
const blueprintList = [
{
id: new Date().toISOString(),
name: 'Getting started',
color: '#c04af6',
code: '<h1>Hello World!</h1>,
},
];
export const BlueprintsContext = createContext();
export const BlueprintsProvider = ({ children }) => {
const [blueprints, setBlueprints] = useState(blueprintList);
let [selected, setSelected] = useState(0);
const [count, setCount] = useState(blueprints.length);
useEffect(() => {
setCount(blueprints.length);
}, [blueprints]);
// just for debugging
useEffect(() => {
console.log(`DEBUG ==> ${selected}`);
}, [selected]);
const create = blueprint => {
blueprint.id = new Date().toISOString();
setBlueprints(blueprints => [...blueprints, blueprint]);
setSelected(count);
};
const remove = blueprint => {
if (count === 1) return;
setBlueprints(blueprints => blueprints.filter(b => b.id !== blueprint.id));
setSelected(-1);
};
const select = blueprint => {
const index = blueprints.indexOf(blueprint);
if (index !== -1) {
setSelected(index);
}
};
const value = {
blueprints,
count,
selected,
select,
create,
remove,
};
return (
<BlueprintsContext.Provider value={value}>
{children}
</BlueprintsContext.Provider>
);
};
Then I'm using the remove function inside the component like this.
const Blueprint = ({ blueprint, isSelected }) => {
const { select, remove } = useContext(BlueprintsContext);
return (
<div
className={classnames(`${CSS.blueprint}`, {
[`${CSS.blueprintActive}`]: isSelected,
})}
key={blueprint.id}
onClick={() => select(blueprint)}
>
<div
className={CSS.blueprintDot}
style={{ backgroundColor: blueprint.color }}
/>
<span
className={classnames(`${CSS.blueprintText}`, {
['text-gray-300']: isSelected,
['text-gray-600']: !isSelected,
})}
>
{blueprint.name}
</span>
{isSelected && (
<IoMdClose className={CSS.close} onClick={() => remove(blueprint)} />
)}
</div>
);
};
The parent component receives a list of items and renders a Blueprint component for each item. The problem is that when the IoMdClose icon is pressed the item will be removed from the list but the selected value won't update to -1. 🤷‍♂️
Ciao, I found your problem: in function select you already set selected:
const select = blueprint => {
const index = blueprints.indexOf(blueprint);
if (index !== -1) {
setSelected(index); // here you set selected
}
};
When you click on IoMdClose icon, also select will be fired. So the result is that this useEffect:
useEffect(() => {
console.log(`DEBUG ==> ${selected}`);
}, [selected]);
doesn't log anything.
I tried to remove setSelected(index); from select function and when I click on IoMdClose icon, selected will be setted to -1 and useEffect logs DEBUG ==> -1.
But now you have another problem: if you remove setSelected(index); from select and you try to select one blueprint from left treeview, blueprint will be not selected. So I re-added setSelected(index); in select function. Removed setSelected(-1); from remove function and now useEffect doesn't log anything!
I think this happens because you are trying to set selected to an index that doesn't exists (because you removed blueprint on icon click). To verify this, I modified setSelected(index); in select function to setSelected(0); and infact now if I remove one blueprint, useEffect will be triggered and logs DEBUG ==> 0.
If the idea behind setSelected(-1); is to deselect all the blueprints in treeview, you could do something like:
export const BlueprintsProvider = ({ children }) => {
....
const removeSelection = useRef(0);
useEffect(() => {
if (blueprints.length < removeSelection.current) setSelected(-1); // if I removed a blueprint, then fire setSelected(-1);
setCount(blueprints.length);
removeSelection.current = blueprints.length; //removeSelection.current setted as blueprints.length
}, [blueprints]);
And of course remove setSelected(-1); from remove function.

Prevent checkboxes from re-rendering in React

I'm trying to render multiple checkboxes based on dynamic return data and have their checked status stored in a local state.
However the performance starts to degrade when higher number of checkboxes are generated. I noticed the issue is due to the constant re-rendering of ALL the checkboxes whenever any one of them is checked (checkbox states are all stored in the same object with different keys)
Here is my sample code and a codesandbox link to see the actual performance issue (notice the delay when a checkbox is selected)
export default function App() {
const [checkboxResponse, setCheckboxResponse] = useState([]);
const [checkboxList, setCheckboxList] = useState();
const [checkboxStates, setCheckboxStates] = useState({});
useEffect(() => {
//Generate dummy data
const dummyData = [];
for (let i = 0; i < 1000; i++) {
dummyData.push(i.toString());
}
//Set dummyData as checkbox dynamic data
setCheckboxResponse(dummyData);
}, []);
useEffect(() => {
//When checkbox is clicked, add to local checkbox states object
const checkboxChange = key => event => {
setCheckboxStates({ ...checkboxStates, [key]: event.target.checked });
};
//Check if data is available
if (checkboxResponse) {
const checkboxes = checkboxResponse.map(key => {
const value = checkboxStates[key] ? checkboxStates[key] : false;
//Render checkbox
return (
<FormControlLabel
key={key}
checked={value}
control={
<Checkbox
size="small"
value={key}
onChange={checkboxChange(key)}
/>
}
label={key}
/>
);
});
setCheckboxList(checkboxes);
}
}, [checkboxResponse, checkboxStates]);
return (
<div className="App">
{checkboxList}
</div>
);
}
CodeSandbox
It seems that whenever checkboxStates is changed, the useEffect hook is re-run, triggering a re-render of all the checkboxes again.
Is it possible to prevent React from re-rendering all the checkboxes again whenever the state is changed? Or do we have to create a separate state for every single checkbox dynamically?
Any help would be greatly appreciated.
You can use React.memo to prevent re-render of unchanged check-boxes. Like this:
import React, { useState, useEffect } from "react";
import { Checkbox, FormControlLabel } from "#material-ui/core";
import "./styles.css";
export default function App() {
const [checkboxResponse, setCheckboxResponse] = useState([]);
const [checkboxStates, setCheckboxStates] = useState({});
//When checkbox is clicked, add to local checkbox states object
const checkboxChange = key => event => {
setCheckboxStates({ ...checkboxStates, [key]: event.target.checked });
};
useEffect(() => {
//Generate dummy data
const dummyData = [];
for (let i = 0; i < 1000; i++) {
dummyData.push(i.toString());
}
//Set dummyData as checkbox dynamic data
setCheckboxResponse(dummyData);
}, []);
return (
<div className="App">
{checkboxResponse.map(key => {
const value = checkboxStates[key] ? checkboxStates[key] : false;
//Render checkbox
return (
<FormControlLabel
key={key}
checked={value}
control={<MemoCheckbox key={key} checkboxChange={checkboxChange} />}
label={key}
/>
);
})}
</div>
);
}
const CustomCheckbox = ({ key, checkboxChange }) => (
<Checkbox size="small" value={key} onChange={checkboxChange(key)} />
);
const MemoCheckbox = React.memo(
CustomCheckbox,
(prev, next) => prev.key === next.key
);
However, it might still be not fast enough as when you click the checkbox it still loops trough .map and creates elements.
Here is docs reference for Memo

Categories

Resources