I want to process setState at once - reactjs

enter image description here
I want to count "indie" and "action" at the same time when the button is clicked. However, the only real application is "action". Please tell me how.

This is my solution to your problem
import React, { useState, useEffect } from "react";
const games = [
{ id: 1, genre: ["indie", "action"] },
{ id: 2, genre: ["indie"] },
{ id: 3, genre: ["action"] }
];
function ButtonComponent(props) {
const { genre, fn } = props;
return <button onClick={() => fn(genre)}>Click</button>;
}
function TestPage() {
const [genre, setGenre] = useState({ indie: 0, action: 0 });
const addGenrecount = (genres) => {
setGenre((previousState) => {
let { indie, action } = previousState;
genres.forEach((genre) => {
if (genre === "indie") indie = indie + 1;
if (genre === "action") action = action + 1;
});
return { indie, action };
});
};
useEffect(() => console.log("genre", genre), [genre]); // Logs to the console when genre change
return games.map((game) => {
const { id, genre } = game;
return <ButtonComponent key={id} genre={genre} fn={addGenrecount} />;
});
}
export default TestPage;
You may also go to codesandbox to test the demo
https://codesandbox.io/s/xenodochial-dirac-q01h4?file=/src/App.js:0-968
Just Friendly Tip:
If you need help regarding react I recommend to upload your code to codesandbox so that we can easily reproduce or solve the problem

Related

setState (array) entries are disappearing

In the following example, User2's messages are getting erased from the state as soon as the User1 sends a message.
User1 sends a message, it gets displayed on screen. As soon as User2 replies, User1's messages disappear.
I'm persuaded it's a React setState mistake but I've followed the react-native-chat-ui's docs as much as possible but somehow there is something going wrong and I cannot put my finger on it.
Here's a video of the bug in action: https://streamable.com/rxbx18
Thank you.
import React, { useEffect, useState } from 'react';
import { Chat, MessageType } from '#flyerhq/react-native-chat-ui'
import { SafeAreaProvider } from 'react-native-safe-area-context'
const uuidv4 = () => {
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c) => {
const r = Math.floor(Math.random() * 16)
const v = c === 'x' ? r : (r % 4) + 8
return v.toString(16)
})
};
const user = { id: uuidv4(), firstName: 'User1' };
const chatbot = { id: uuidv4(), firstName: 'User2' };
const App = () => {
const [messages, setMessages] = useState<MessageType.Any[]>([])
const addMessage = (message: MessageType.Any) => {
setMessages([message, ...messages]);
};
const handleSendPress = (message: MessageType.PartialText) => {
// display user message
const textMessage: MessageType.Text = {
author : user,
createdAt: Date.now(),
id : uuidv4(),
text : message.text,
type : 'text',
};
addMessage(textMessage);
// display bot message
// NOTE: adding a timeout so that you can see user's message for a second...
setTimeout(() => {
const chatbotTextMessage: MessageType.Text = {
author : chatbot,
createdAt: Date.now(),
id : uuidv4(),
text : `Response that will erase user's messages...`,
type : 'text',
};
addMessage(chatbotTextMessage);
}, 1000);
};
return (
<SafeAreaProvider>
<Chat
messages={messages}
showUserNames={true}
onSendPress={handleSendPress}
user={user}
/>
</SafeAreaProvider>
);
}
export default App;

How useStoreState is referring values in react?

I am pretty new to react and easy-peasy, I am stuck with one implementation with useStoreState.
I just want to understand how the useStoreState and useStoreAction work in the below code and how state.AdminCompanyInfo and action.AdminCompanyInfo are getting resolved.
I don't find any simple-to-understand example. Please help.
here is the code
const AdminCompanyInfo = () => {
var userType = isDesignerUser ? 1 : 2;
const [hideInactiveUser, setHideInactiveUser] = useState();
const {
roles: { data: roles },
companyUsers: state,
inactiveUserSetting: { response: orgSetting },
} = useStoreState(state => state.AdminCompanyInfo);
const {
companyUsers: { fetch },
companyUsers: actions,
roles: { fetch: fetchRoles },
inactiveUserSetting: { update: updateOrgSetting },
} = useStoreActions(actions => actions.AdminCompanyInfo);
useEffect(() => {
fetchRoles(userType);
fetch();
}, []);
}

Get TypeError: Cannot read properties of undefined (reading 'forEach') when pass a params

At first, the params does not have any data yet (blank array), but it will update again after useEffect set the variable.
But for my highchart, it gave me this error.
TypeError: Cannot read properties of undefined (reading 'forEach')
52 | createChart();
53 | } else {
54 | if (props.allowChartUpdate !== false) {
>55 | if (!props.immutable && chartRef.current) {
| ^ 56 | chartRef.current.update(
57 | props.options,
58 | ...(props.updateArgs || [true, true])
I searched some solutions, they suggest can use allowChartUpdate={false} and immutable={false} for solving the problem. After I tried it, yes it does solve my problem, but my highchart doesn't show the data when initial load.
I'm guessing is it the params passing in a blank array at first, then passing a second time with actual values so it causes this problem. If yes, can rerendering the highchart solve the problem? And how can I do that?
Here is the link, please help me on it. Thank you muakzzz.
You can instead just provide the getRouteData function as an initializer function directly to the useState hook to provide the initial state. So long as it's not asynchronous it will provide initial state for the initial render, no need to use the useEffect hook to populate state after the first render.
Additionally, you should initialize routeMapData to have the data property array by default so you don't accidentally pass it through with an undefined data property, which was part of the problem you were seeing.
export default function App() {
const [routeData] = useState(getRouteData()); // <-- initialize state
const mapStation = () => {
const routeMapData = {
data: [], // <-- data array to push into
};
if (routeData.length !== 0) {
for (let i = 0; i < routeData.length; i++) {
const station = routeData[i].station;
for (let j = 0; j < station.length; j++) {
const firstStation = station[j];
const nextStation = station[j + 1];
if (nextStation) {
routeMapData.data.push([ // <-- push into array
firstStation.stationName,
nextStation.stationName
]);
}
}
}
}
return routeMapData;
};
const content = (key) => {
if (key === "map") {
return <RouteMap mapRouteData={mapStation()} />;
}
return null;
};
return <Box className="rightPaper center">{content("map")}</Box>;
}
You don't need to even use the local state as you can directly consume the returned array from getRouteData in the mapStation utility function.
export default function App() {
const mapStation = () => {
return {
data: getRouteData().flatMap(({ station }) => {
return station.reduce((segments, current, i, stations) => {
if (stations[i + 1]) {
segments.push([
current.stationName,
stations[i + 1].stationName
]);
}
return segments;
}, []);
})
};
};
const content = (key) => {
if (key === "map") {
return <RouteMap mapRouteData={mapStation()} />;
}
return null;
};
return <Box className="rightPaper center">{content("map")}</Box>;
}
Thank you for your help. I managed to get my desired output already. The problem was my parent component will pass a blank array of data into my highchart network graph component at first due to the useEffect used in the parent. And after that, they pass another array with actual data into my highchart network graph component.
import React, {useEffect, useState} from 'react';
import {Box} from "#mui/material";
import RouteMap from "./Content/RouteMap";
import {getRouteData} from "../../../API/RouteDataAPI"
import Timetable from "./Content/Timetable";
import _ from 'lodash';
function RightContent({contentKey}) {
const [routeData, setRouteData] = useState([]);
useEffect(() => {
getRouteData().then(res => setRouteData(res));
}, [])
const mapStation = () => {
let arr = [], allStation = [], routeMapData = {}
if (routeData.length !== 0) {
for (let i = 0; i < routeData.length; i++) {
const station = routeData[i].station;
for (let j = 0; j < station.length; j++) {
const firstStation = station[j];
const nextStation = station[j + 1];
allStation.push(firstStation.stationName)
if (nextStation) {
arr.push([firstStation.stationName, nextStation.stationName])
}
}
}
routeMapData.data = arr;
routeMapData.allStation = allStation;
routeMapData.centralStation = "KL Sentral"
}
return routeMapData;
}
// const mapStation = () => {
// let arr = [];
// getRouteData().then(res => {
// arr.push(res.flatMap(({station}) => {
// return station.reduce((segments, current, i, stations) => {
// if (stations[i + 1]) {
// segments.push(
// current.stationName,
// );
// }
// return segments;
// }, []);
// }))
// })
// console.log(arr)
// }
const content = (key) => {
const availableRoute = routeData.map(route => route.routeTitle);
if (
key === 'map'
// && !_.isEmpty(mapStation())
){
// console.log('here', mapRouteData)
return <RouteMap mapRouteData={mapStation()}/>;
}
else if (availableRoute.includes(key)) {
return <Timetable routeData={routeData} currentRoute={key}/>
} else {
return null;
}
}
return (
<Box className="rightPaper center">
{content(contentKey)}
</Box>
);
}
export default RightContent;
^^^This was my parent component.^^^
In the content variable function there, I have an if statement with the requirements provided. If you try to uncomment the lodash (second requirement) in the if statement, I can able to get my desire result.
This was my highchart network component.
import React, {useEffect, useRef, useState} from 'react'
import Highcharts from 'highcharts/highstock'
import HighchartsReact from 'highcharts-react-official'
import networkgraph from 'highcharts/modules/networkgraph'
require('highcharts/modules/exporting')(Highcharts);
require('highcharts/modules/export-data')(Highcharts);
if (typeof Highcharts === "object") {
networkgraph(Highcharts);
}
const RouteMap = ({mapRouteData}) => {
const [seriesData, setSeriesData] = useState(mapRouteData.data);
const [centralStation, setCentralStation] = useState(mapRouteData.centralStation);
const [allStation, setAllStation] = useState(mapRouteData.allStation);
useEffect(() => {
setSeriesData(mapRouteData.data);
setCentralStation(mapRouteData.centralStation);
setAllStation(mapRouteData.allStation);
}, [mapRouteData])
Highcharts.addEvent(
Highcharts.Series,
'afterSetOptions',
function (e) {
let colors = Highcharts.getOptions().colors,
i = 0,
nodes = {};
if (
this instanceof Highcharts.seriesTypes.networkgraph &&
e.options.id === 'lang-tree' &&
e.options.data !== undefined
) {
let lastSecond = '', arry = []
e.options.data.forEach(function (link) {
if (lastSecond !== link[0]) {
nodes[link[0]] = {
id: link[0],
color: colors[++i]
}
} else if (lastSecond === link[0]) {
nodes[link[0]] = {
id: link[0],
color: colors[i]
}
nodes[link[1]] = {
id: link[1],
color: colors[i]
}
arry.push(link[0])
}
lastSecond = link[1];
});
const exchangeStation = allStation.filter((item, index) => allStation.indexOf(item) !== index);
i += 1;
exchangeStation.forEach((station) => {
nodes[station] = {
id: station,
marker: {
radius: 18
},
name: 'Interchange: ' + station,
color: colors[i]
}
})
nodes[centralStation] = {
id: centralStation,
name: 'Sentral Station: ' + centralStation,
marker: {
radius: 25
},
color: colors[++i]
}
e.options.nodes = Object.keys(nodes).map(function (id) {
return nodes[id];
});
}
}
);
const options = {
chart: {
type: 'networkgraph',
},
title: {
text: 'The Route Map'
},
caption: {
text: "Click the button at top right for more options."
},
credits: {
enabled: false
},
plotOptions: {
networkgraph: {
keys: ['from', 'to'],
layoutAlgorithm: {
enableSimulation: true,
// linkLength: 7
}
}
},
series: [
{
link: {
width: 4,
},
marker: {
radius: 10
},
dataLabels: {
enabled: true,
linkFormat: "",
allowOverlap: false
},
id: "lang-tree",
data: seriesData
}
]
};
return <HighchartsReact
ref={useRef()}
containerProps={{style: {height: "100%", width: "100%"}}}
highcharts={Highcharts}
options={options}
/>;
}
export default RouteMap;
Sorry for the lengthy code here. By the way, feel free to let me know any improvements I can make in my code. First touch on react js project and still have a long journey to go.
Once again~ Thank you!
I fixed adding True for allowChartUpdate and immutable
<HighchartsReact
ref={chartRef}
highcharts={Highcharts}
options={options}
containerProps={containerProps}
allowChartUpdate={true}
immutable={true}
/>

Array data from Graphql is not populating a component in React

I am trying to render multiple checkboxes that have two functions:
Show two states: checked and unchecked
Update a checked array with the checked checkboxes
Currently, I am successfully able to accomplish these two goals with dummy data:
const dummyPlayers = [
{ id: 1, name: 'Puppa' },
{ id: 2, name: 'Korvo' },
{ id: 3, name: 'Jesse' },
{ id: 4, name: 'Terry' },
{ id: 5, name: 'Gobblins' }
]
This is the shape of the array I want to populate the checkboxes with:
[
{
"id": "936d6050-00df-4bd4-bc54-6ce58ad0210c",
"name": "Travis",
"owner": "moralesfam",
"type": "Member",
"createdAt": "2021-09-24T20:08:02.292Z",
"updatedAt": "2021-09-24T20:08:02.292Z"
}...
]
However, when I start pulling data in from a database with Graphql, while I am able to render the checkboxes to the DOM, they are not interactive (don't show checked state) and don't log the checked checkboxes.
I bring in the data, an array of objects through a custom React hook, called useMembers and the data is stored in a members array. Console logging members prints out the array, but as soon as I swap the dummyPlayers for the members array, the two goals I stated earlier are unsuccessful.
// RecordGame.js
import React, { useState } from 'react'
import useLoadMembers from '../hooks/useLoadMembers'
import useUser from '../hooks/useUser'
function RecordGame() {
const dummyPlayers = [
{ id: 1, name: 'Puppa' },
{ id: 2, name: 'Korvo' },
{ id: 3, name: 'Jesse' },
{ id: 4, name: 'Terry' },
{ id: 5, name: 'Gobblins' },
]
const { members } = useLoadMembers(updateLoading)
const { user } = useUser()
const [checkedState, setCheckedState] = useState(
new Array(members.length).fill(false)
)
let playingPlayers = []
for (var index in checkedState) {
if (checkedState[index] === true) {
playingPlayers.push(dummyPlayers[index])
}
}
console.log(playingPlayers)
const handleOnChange = (position) => {
const updatedCheckedState = checkedState.map((player, index) =>
index === position ? !player : player
)
setCheckedState(updatedCheckedState)
}
// Rendered elements
const playerCheckboxes = dummyPlayers.map((player, index) => {
return (
<div key={index}>
<label htmlFor={player.name}>
<input
type="checkbox"
id={player.name}
name={player.name}
checked={checkedState[index]}
onChange={() => handleOnChange(index)}
/>
<span> {player.name}</span>
</label>
</div>
)
})
return (
<div>
<form>
{/* Game Players */}
<div>
<label htmlFor="players">
Who Played?
</label>
<div>{playerCheckboxes}</div>
</div>
</form>
</div>
)}
</Dashboard>
)
}
export default RecordGame
//useLoadMember.js
import { useState, useEffect } from 'react'
import { API, Auth } from 'aws-amplify'
import { listMembers } from '../graphql/queries'
const useLoadMembers = (updateLoading) => {
const [members, updateMembers] = useState([])
useEffect(() => {
fetchMembers()
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [])
const fetchMembers = async () => {
try {
let memberData = await API.graphql({
query: listMembers,
variables: { limit: 100 },
})
updateLoading(false)
let allMembers = memberData.data.listMembers.items
setFilteredMembers(allMembers)
} catch (err) {
console.error(err)
}
}
const setFilteredMembers = async (allMembers) => {
const { username } = await Auth.currentAuthenticatedUser()
const myMemberData = allMembers.filter((p) => p.owner === username)
updateMembers(myMemberData)
}
return { members }
}
export default useLoadMembers
In this first, picture, I used the dummyPlayers array and got the results I wanted.
However, in this second screenshot, I replaced the dummyData with the members array and did not get any results I wanted.
I'm just confused on why I am getting different results with the same array shape.
wherever you use members you will need to check that members is not undefined before it gets used. If the api calls are not complete, it will get set initially as undefined.
eg: members && members.map(() => ....)

useSelector doesnot update the UI

I have a nested state like :
bookingDetails = {
jobCards: [
{
details_id: '1',
parts: [
{...},
{...}
]
}
]}
Now I got the respective jobCards in component from props from parent component i.e detailsID by using useSelector:
const jobCard = useSelector(state => state.bookingDetails.jobCards.find(item => item.details_id === detailsID))
I got a button that successfully adds new object in parts in respective jobCards but that doesnot update the UI.
My bookingDetails Reducer:
case 'ADD_PARTS':
return {
...state,
jobCards: state.jobCards.map(jobCard => {
if (jobCard.details_id === action.id) {
jobCard.parts = [...jobCard.parts, { _id: uuid(), name: '' }]
}
return jobCard
})
}
use like this
const [isJobUpdated, setIsJobUpdated] = useState(false);
const jobCard = useSelector(state => state.bookingDetails.jobCards.find(item => item.details_id === detailsID))
useEffect(() => {
setIsJobUpdated(!!jobCard.length);
}, [jobCard])
return (
<>
{isJobUpdated && <YourComponent />
</>
)
NOTE: this is not the best way to do. You might face re-render issue. Just to check if this solve your current issue.
Forgot to add return statement.
The reducer should have been:
case 'ADD_PARTS':
return {
...state,
jobCards: state.jobCards.map(jobCard => {
if (jobCard.details_id === action.id) {
return {
...jobCard,
parts: [...jobCard.parts, { id: uuid(), name: ''}]
}
}
return jobCard
})
}

Resources