From 1dd6b76c03faae0ab8f4809f2cb417dcc9737ffc Mon Sep 17 00:00:00 2001 From: Justin Wyne <1986068+wyne@users.noreply.github.com> Date: Sun, 21 Apr 2024 01:33:59 -0700 Subject: [PATCH 1/7] Fix extra renders of player tiles --- redux/GamesSlice.ts | 23 ++++++++++++++------ src/components/Boards/FlexboxTile.tsx | 12 ++++++---- src/components/ScoreLog/PlayerNameColumn.tsx | 5 +++-- src/components/ScoreLog/SortHelper.ts | 1 - 4 files changed, 27 insertions(+), 14 deletions(-) diff --git a/redux/GamesSlice.ts b/redux/GamesSlice.ts index 8253d4d4..3b7907ed 100644 --- a/redux/GamesSlice.ts +++ b/redux/GamesSlice.ts @@ -204,17 +204,26 @@ export const selectSortSelectorKey = (state: RootState, gameId: string) => { return key !== undefined ? key : SortSelectorKey.ByScore; }; -const selectPaletteName = (state: RootState, gameId: string) => state.games.entities[gameId]?.palette; -const selectPlayerIndex = (_: RootState, __: string, playerIndex: number) => playerIndex; - -export const selectPlayerColors = createSelector( - [selectPaletteName, selectPlayerIndex], - (paletteName, playerIndex) => { +export const makeSelectPlayerColors = () => createSelector( + [ + (state: RootState, gameId: string) => state.games.entities[gameId], + (state: RootState, gameId: string, playerId: string) => state.players.entities[playerId], + ], + (game, player) => { // TODO: Get player color if it exists + if (!game || !player) { + return ['#000000', '#FFFFFF']; + } + + const paletteName = game.palette; + const playerIds = game.playerIds; + + const index = playerIds.indexOf(player.id); + const palette = getPalette(paletteName || 'original'); - const bg = palette[playerIndex % palette.length]; + const bg = palette[index % palette.length]; const blackContrast = getContrastRatio(bg, '#000').number; const whiteContrast = getContrastRatio(bg, '#fff').number; diff --git a/src/components/Boards/FlexboxTile.tsx b/src/components/Boards/FlexboxTile.tsx index 8723e13d..794ef879 100644 --- a/src/components/Boards/FlexboxTile.tsx +++ b/src/components/Boards/FlexboxTile.tsx @@ -3,7 +3,7 @@ import React from 'react'; import { DimensionValue, StyleSheet } from 'react-native'; import Animated, { Easing, FadeIn } from 'react-native-reanimated'; -import { selectPlayerColors } from '../../../redux/GamesSlice'; +import { makeSelectPlayerColors } from '../../../redux/GamesSlice'; import { useAppSelector } from '../../../redux/hooks'; import { selectCurrentGame, selectInteractionType } from '../../../redux/selectors'; import { interactionComponents } from '../Interactions/InteractionComponents'; @@ -20,7 +20,7 @@ interface Props { height: number; } -const FlexboxTile: React.FunctionComponent = ({ +const FlexboxTile: React.FunctionComponent = React.memo(({ index, width, height, @@ -33,8 +33,12 @@ const FlexboxTile: React.FunctionComponent = ({ if (Number.isNaN(width) || Number.isNaN(height)) return null; const currentGameId = useAppSelector(state => selectCurrentGame(state)?.id); + const playerIndexLabel = useAppSelector(state => state.settings.showPlayerIndex); - const playerColors = useAppSelector(state => selectPlayerColors(state, currentGameId || '', index || 0)); + + const selectPlayerColors = makeSelectPlayerColors(); + const playerColors = useAppSelector(state => selectPlayerColors(state, currentGameId || '', playerId)); + const [bg, fg] = playerColors; const widthPerc: DimensionValue = `${(100 / cols)}%`; @@ -67,7 +71,7 @@ const FlexboxTile: React.FunctionComponent = ({ ); -}; +}); const styles = StyleSheet.create({ playerCard: { diff --git a/src/components/ScoreLog/PlayerNameColumn.tsx b/src/components/ScoreLog/PlayerNameColumn.tsx index 525f0fc8..af434447 100644 --- a/src/components/ScoreLog/PlayerNameColumn.tsx +++ b/src/components/ScoreLog/PlayerNameColumn.tsx @@ -3,7 +3,7 @@ import React from 'react'; import { StyleSheet, Text, View } from 'react-native'; import { Icon } from 'react-native-elements/dist/icons/Icon'; -import { selectPlayerColors } from '../../../redux/GamesSlice'; +import { makeSelectPlayerColors } from '../../../redux/GamesSlice'; import { useAppSelector } from '../../../redux/hooks'; import { selectPlayerById } from '../../../redux/PlayersSlice'; import { selectCurrentGame } from '../../../redux/selectors'; @@ -18,7 +18,8 @@ interface CellProps { const PlayerNameCell: React.FunctionComponent = ({ index, playerId }) => { const currentGameId = useAppSelector(state => selectCurrentGame(state)?.id); - const playerColors = useAppSelector(state => selectPlayerColors(state, currentGameId || '', index || 0)); + const selectPlayerColors = makeSelectPlayerColors(); + const playerColors = useAppSelector(state => selectPlayerColors(state, currentGameId || '', playerId)); const playerName = useAppSelector(state => selectPlayerById(state, playerId)?.playerName); return ( diff --git a/src/components/ScoreLog/SortHelper.ts b/src/components/ScoreLog/SortHelper.ts index 202884bc..ff8eb4d3 100644 --- a/src/components/ScoreLog/SortHelper.ts +++ b/src/components/ScoreLog/SortHelper.ts @@ -35,7 +35,6 @@ export const selectPlayerIdsByScore: SortSelector = createSelector( (state: RootState) => state.settings.currentGameId ? state.games.entities[state.settings.currentGameId]?.sortDirectionKey : undefined ], (players: ScoreState[], currentGame: GameState | undefined, sortDirectionKey: SortDirectionKey | undefined) => { - console.log('sort selector'); if (!currentGame) return []; const playerIds = [...players] From 327077dadadcbb62b918c471514665d4e9e58af3 Mon Sep 17 00:00:00 2001 From: Justin Wyne <1986068+wyne@users.noreply.github.com> Date: Sun, 21 Apr 2024 11:30:26 -0700 Subject: [PATCH 2/7] Fix extra renders --- redux/GamesSlice.ts | 8 ++++---- redux/PlayersSlice.ts | 11 ++++++++++- src/components/PlayerListItem.tsx | 6 ++++-- src/components/Rounds.tsx | 11 ++++------- src/components/ScoreLog/PlayerNameColumn.tsx | 13 ++++++++----- src/components/ScoreLog/RoundScoreColumn.tsx | 9 +++++---- src/screens/ShareScreen.tsx | 11 +++-------- 7 files changed, 38 insertions(+), 31 deletions(-) diff --git a/redux/GamesSlice.ts b/redux/GamesSlice.ts index 3b7907ed..501f9a72 100644 --- a/redux/GamesSlice.ts +++ b/redux/GamesSlice.ts @@ -207,19 +207,19 @@ export const selectSortSelectorKey = (state: RootState, gameId: string) => { export const makeSelectPlayerColors = () => createSelector( [ (state: RootState, gameId: string) => state.games.entities[gameId], - (state: RootState, gameId: string, playerId: string) => state.players.entities[playerId], + (state: RootState, gameId: string, playerId: string) => playerId, ], - (game, player) => { + (game, playerId) => { // TODO: Get player color if it exists - if (!game || !player) { + if (!game || !playerId) { return ['#000000', '#FFFFFF']; } const paletteName = game.palette; const playerIds = game.playerIds; - const index = playerIds.indexOf(player.id); + const index = playerIds.indexOf(playerId); const palette = getPalette(paletteName || 'original'); diff --git a/redux/PlayersSlice.ts b/redux/PlayersSlice.ts index 237fc932..5129a8ae 100644 --- a/redux/PlayersSlice.ts +++ b/redux/PlayersSlice.ts @@ -1,5 +1,7 @@ import crashlytics from '@react-native-firebase/crashlytics'; -import { PayloadAction, createEntityAdapter, createSlice } from '@reduxjs/toolkit'; +import { PayloadAction, createEntityAdapter, createSelector, createSlice } from '@reduxjs/toolkit'; + +import { RootState } from './store'; type RoundIndex = number; @@ -72,3 +74,10 @@ export const { selectIds: selectPlayerIds, // Pass in a selector that returns the posts slice of state } = playersAdapter.getSelectors((state: PlayersSlice) => state.players); + +export const selectPlayerNameById = createSelector( + [ + (state: RootState, playerId: string) => state.players.entities[playerId] + ], + (player) => player?.playerName +); diff --git a/src/components/PlayerListItem.tsx b/src/components/PlayerListItem.tsx index ed03e192..fbe9b1a6 100644 --- a/src/components/PlayerListItem.tsx +++ b/src/components/PlayerListItem.tsx @@ -6,7 +6,7 @@ import { NativeStackNavigationProp } from '@react-navigation/native-stack'; import { Alert, StyleSheet, Text, TouchableOpacity, View } from 'react-native'; import { Icon, ListItem } from 'react-native-elements'; -import { selectGameById, selectPlayerColors, updateGame } from '../../redux/GamesSlice'; +import { makeSelectPlayerColors, selectGameById, updateGame } from '../../redux/GamesSlice'; import { useAppDispatch, useAppSelector } from '../../redux/hooks'; import { removePlayer, selectPlayerById } from '../../redux/PlayersSlice'; import { selectCurrentGame } from '../../redux/selectors'; @@ -31,7 +31,9 @@ const PlayerListItem: React.FunctionComponent = ({ const currentGameId = useAppSelector(state => selectCurrentGame(state)?.id); const playerIds = useAppSelector(state => selectGameById(state, currentGameId || '')?.playerIds); const player = useAppSelector(state => selectPlayerById(state, playerId)); - const playerColors = useAppSelector(state => selectPlayerColors(state, currentGameId || '', index || 0)); + + const selectPlayerColors = makeSelectPlayerColors(); + const playerColors = useAppSelector(state => selectPlayerColors(state, currentGameId || '', playerId)); if (currentGameId == '' || currentGameId === undefined) return null; if (playerIds === undefined) return null; diff --git a/src/components/Rounds.tsx b/src/components/Rounds.tsx index 437f9199..e65448b1 100644 --- a/src/components/Rounds.tsx +++ b/src/components/Rounds.tsx @@ -5,12 +5,12 @@ import { NativeStackNavigationProp } from '@react-navigation/native-stack'; import { LayoutChangeEvent, Platform, ScrollView, StyleSheet, View } from 'react-native'; import { TouchableOpacity } from 'react-native-gesture-handler'; -import { selectGameById, selectSortSelectorKey, setSortSelector } from '../../redux/GamesSlice'; +import { selectGameById, setSortSelector } from '../../redux/GamesSlice'; import { useAppDispatch, useAppSelector } from '../../redux/hooks'; import PlayerNameColumn from './ScoreLog/PlayerNameColumn'; import RoundScoreColumn from './ScoreLog/RoundScoreColumn'; -import { SortSelectorKey, sortSelectors } from './ScoreLog/SortHelper'; +import { SortSelectorKey } from './ScoreLog/SortHelper'; import TotalScoreColumn from './ScoreLog/TotalScoreColumn'; interface Props { @@ -61,9 +61,6 @@ const Rounds: React.FunctionComponent = ({ }) => { const dispatch = useAppDispatch(); - const sortSelectorKey = useAppSelector(state => selectSortSelectorKey(state, currentGameId)); - const sortSelector = sortSelectors[sortSelectorKey]; - const sortByPlayerIndex = () => { dispatch(setSortSelector({ gameId: currentGameId, sortSelector: SortSelectorKey.ByIndex })); }; @@ -88,10 +85,10 @@ const Rounds: React.FunctionComponent = ({ }) => { {roundsIterator.map((item, round) => ( onLayoutHandler(e, round)}> + isCurrentRound={round == roundCurrent} + /> ))} diff --git a/src/components/ScoreLog/PlayerNameColumn.tsx b/src/components/ScoreLog/PlayerNameColumn.tsx index af434447..0ff03d3e 100644 --- a/src/components/ScoreLog/PlayerNameColumn.tsx +++ b/src/components/ScoreLog/PlayerNameColumn.tsx @@ -1,11 +1,11 @@ -import React from 'react'; +import React, { useMemo } from 'react'; import { StyleSheet, Text, View } from 'react-native'; import { Icon } from 'react-native-elements/dist/icons/Icon'; import { makeSelectPlayerColors } from '../../../redux/GamesSlice'; import { useAppSelector } from '../../../redux/hooks'; -import { selectPlayerById } from '../../../redux/PlayersSlice'; +import { selectPlayerNameById } from '../../../redux/PlayersSlice'; import { selectCurrentGame } from '../../../redux/selectors'; import { SortDirectionKey, SortSelectorKey, sortSelectors } from './SortHelper'; @@ -18,9 +18,11 @@ interface CellProps { const PlayerNameCell: React.FunctionComponent = ({ index, playerId }) => { const currentGameId = useAppSelector(state => selectCurrentGame(state)?.id); - const selectPlayerColors = makeSelectPlayerColors(); + + const selectPlayerColors = useMemo(() => makeSelectPlayerColors(), []); const playerColors = useAppSelector(state => selectPlayerColors(state, currentGameId || '', playerId)); - const playerName = useAppSelector(state => selectPlayerById(state, playerId)?.playerName); + + const playerName = useAppSelector(state => selectPlayerNameById(state, playerId)); return ( @@ -56,6 +58,7 @@ const PlayerHeaderCell: React.FunctionComponent = () => { ); }; +const MemoizedPlayerNameCell = React.memo(PlayerNameCell); const PlayerNameColumn: React.FunctionComponent = () => { const sortKey = useAppSelector(state => selectCurrentGame(state)?.sortSelectorKey); @@ -68,7 +71,7 @@ const PlayerNameColumn: React.FunctionComponent = () => { {sortedPlayerIds.map((playerId, index) => ( - playerId && + playerId && ))} ); diff --git a/src/components/ScoreLog/RoundScoreColumn.tsx b/src/components/ScoreLog/RoundScoreColumn.tsx index 8a1ac19b..ae588f05 100644 --- a/src/components/ScoreLog/RoundScoreColumn.tsx +++ b/src/components/ScoreLog/RoundScoreColumn.tsx @@ -3,17 +3,16 @@ import React, { memo, useCallback } from 'react'; import analytics from '@react-native-firebase/analytics'; import { LayoutChangeEvent, Text, TouchableWithoutFeedback, View } from 'react-native'; -import { updateGame } from '../../../redux/GamesSlice'; +import { selectSortSelectorKey, updateGame } from '../../../redux/GamesSlice'; import { useAppDispatch, useAppSelector } from '../../../redux/hooks'; -import { RootState } from '../../../redux/store'; import RoundScoreCell from './RoundScoreCell'; +import { sortSelectors } from './SortHelper'; interface Props { round: number; isCurrentRound: boolean; disabled?: boolean; - sortSelector: (state: RootState) => string[]; onLayout?: (event: LayoutChangeEvent, round: number) => void; } @@ -21,13 +20,15 @@ const RoundScoreColumn: React.FunctionComponent = ({ round, isCurrentRound, disabled = false, - sortSelector, onLayout, }) => { const dispatch = useAppDispatch(); const currentGameId = useAppSelector(state => state.settings.currentGameId); + const sortSelectorKey = useAppSelector(state => selectSortSelectorKey(state, currentGameId || '')); + const sortSelector = sortSelectors[sortSelectorKey]; + const sortedPlayerIds = useAppSelector(sortSelector); const onPressHandler = useCallback(async () => { diff --git a/src/screens/ShareScreen.tsx b/src/screens/ShareScreen.tsx index 70c2dca4..82e0757c 100644 --- a/src/screens/ShareScreen.tsx +++ b/src/screens/ShareScreen.tsx @@ -9,12 +9,11 @@ import { Button, Icon } from 'react-native-elements'; import { SafeAreaView } from 'react-native-safe-area-context'; import { captureRef } from 'react-native-view-shot'; -import { selectGameById, selectSortSelectorKey } from '../../redux/GamesSlice'; +import { selectGameById } from '../../redux/GamesSlice'; import { useAppSelector } from '../../redux/hooks'; import { selectCurrentGame } from '../../redux/selectors'; import PlayerNameColumn from '../components/ScoreLog/PlayerNameColumn'; import RoundScoreColumn from '../components/ScoreLog/RoundScoreColumn'; -import { sortSelectors } from '../components/ScoreLog/SortHelper'; import TotalScoreColumn from '../components/ScoreLog/TotalScoreColumn'; import { systemBlue } from '../constants'; @@ -54,9 +53,6 @@ const ShareScreen: React.FunctionComponent = ({ navigation }) => { await analytics().logEvent('share_image'); }; - const sortSelectorKey = useAppSelector(state => selectSortSelectorKey(state, currentGameId)); - const sortSelector = sortSelectors[sortSelectorKey]; - return ( @@ -98,12 +94,11 @@ const ShareScreen: React.FunctionComponent = ({ navigation }) => { - - + + {roundsIterator.map((item, round) => ( Date: Sun, 21 Apr 2024 11:52:17 -0700 Subject: [PATCH 3/7] Fix rerender of score cells --- redux/PlayersSlice.ts | 7 +++++++ src/components/ScoreLog/RoundScoreCell.tsx | 13 ++++--------- 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/redux/PlayersSlice.ts b/redux/PlayersSlice.ts index 5129a8ae..bb2142e9 100644 --- a/redux/PlayersSlice.ts +++ b/redux/PlayersSlice.ts @@ -81,3 +81,10 @@ export const selectPlayerNameById = createSelector( ], (player) => player?.playerName ); + +export const makeSelectPlayerScoreByRound = () => createSelector( + [ + (state: RootState, playerId: string, round: number) => state.players.entities[playerId]?.scores[round] + ], + (score) => score || 0 +); diff --git a/src/components/ScoreLog/RoundScoreCell.tsx b/src/components/ScoreLog/RoundScoreCell.tsx index f942835f..1ac5701b 100644 --- a/src/components/ScoreLog/RoundScoreCell.tsx +++ b/src/components/ScoreLog/RoundScoreCell.tsx @@ -1,9 +1,9 @@ import React, { memo } from 'react'; -import { Text, StyleSheet } from 'react-native'; +import { StyleSheet, Text } from 'react-native'; import { useAppSelector } from '../../../redux/hooks'; -import { selectPlayerById } from '../../../redux/PlayersSlice'; +import { makeSelectPlayerScoreByRound, selectPlayerScoreByRound } from '../../../redux/PlayersSlice'; interface Props { playerId: string; @@ -12,13 +12,8 @@ interface Props { } const RoundScoreCell: React.FunctionComponent = ({ playerId, round, playerIndex }) => { - const scores = useAppSelector(state => - selectPlayerById(state, playerId)?.scores - ); - - if (typeof scores == 'undefined') return null; - - const scoreRound = scores[round] || 0; + const selectPlayerScoreByRound = makeSelectPlayerScoreByRound(); + const scoreRound = useAppSelector(state => selectPlayerScoreByRound(state, playerId, round)); return ( Date: Sun, 21 Apr 2024 15:20:02 -0700 Subject: [PATCH 4/7] Optimize score table renders --- redux/PlayersSlice.ts | 2 +- src/components/ScoreLog/PlayerNameColumn.tsx | 1 - src/components/ScoreLog/RoundScoreCell.tsx | 3 +-- src/components/ScoreLog/SortHelper.ts | 23 ++++++++++---------- src/screens/SettingsScreen.tsx | 4 ++-- 5 files changed, 15 insertions(+), 18 deletions(-) diff --git a/redux/PlayersSlice.ts b/redux/PlayersSlice.ts index bb2142e9..f3d68d9e 100644 --- a/redux/PlayersSlice.ts +++ b/redux/PlayersSlice.ts @@ -82,7 +82,7 @@ export const selectPlayerNameById = createSelector( (player) => player?.playerName ); -export const makeSelectPlayerScoreByRound = () => createSelector( +export const selectPlayerScoreByRound = createSelector( [ (state: RootState, playerId: string, round: number) => state.players.entities[playerId]?.scores[round] ], diff --git a/src/components/ScoreLog/PlayerNameColumn.tsx b/src/components/ScoreLog/PlayerNameColumn.tsx index 0ff03d3e..adf5fe4d 100644 --- a/src/components/ScoreLog/PlayerNameColumn.tsx +++ b/src/components/ScoreLog/PlayerNameColumn.tsx @@ -62,7 +62,6 @@ const MemoizedPlayerNameCell = React.memo(PlayerNameCell); const PlayerNameColumn: React.FunctionComponent = () => { const sortKey = useAppSelector(state => selectCurrentGame(state)?.sortSelectorKey); - const sortSelector = sortSelectors[sortKey || SortSelectorKey.ByIndex]; const sortedPlayerIds = useAppSelector(sortSelector); diff --git a/src/components/ScoreLog/RoundScoreCell.tsx b/src/components/ScoreLog/RoundScoreCell.tsx index 1ac5701b..898a93ea 100644 --- a/src/components/ScoreLog/RoundScoreCell.tsx +++ b/src/components/ScoreLog/RoundScoreCell.tsx @@ -3,7 +3,7 @@ import React, { memo } from 'react'; import { StyleSheet, Text } from 'react-native'; import { useAppSelector } from '../../../redux/hooks'; -import { makeSelectPlayerScoreByRound, selectPlayerScoreByRound } from '../../../redux/PlayersSlice'; +import { selectPlayerScoreByRound } from '../../../redux/PlayersSlice'; interface Props { playerId: string; @@ -12,7 +12,6 @@ interface Props { } const RoundScoreCell: React.FunctionComponent = ({ playerId, round, playerIndex }) => { - const selectPlayerScoreByRound = makeSelectPlayerScoreByRound(); const scoreRound = useAppSelector(state => selectPlayerScoreByRound(state, playerId, round)); return ( diff --git a/src/components/ScoreLog/SortHelper.ts b/src/components/ScoreLog/SortHelper.ts index ff8eb4d3..260c92ff 100644 --- a/src/components/ScoreLog/SortHelper.ts +++ b/src/components/ScoreLog/SortHelper.ts @@ -4,23 +4,22 @@ import { GameState } from '../../../redux/GamesSlice'; import { ScoreState, selectAllPlayers } from '../../../redux/PlayersSlice'; import { RootState } from '../../../redux/store'; -export const selectPlayerIdsByIndex: SortSelector = createSelector( +export const selectSortedPlayerIdsByIndex = createSelector( [ - selectAllPlayers, (state: RootState) => state.settings.currentGameId ? state.games.entities[state.settings.currentGameId] : undefined, (state: RootState) => state.settings.currentGameId ? state.games.entities[state.settings.currentGameId]?.sortDirectionKey : undefined ], - (players: ScoreState[], currentGame: GameState | undefined) => { - if (!currentGame) return []; + (game: GameState | undefined, sortDirectionKey: SortDirectionKey | undefined) => { + if (!game) return []; - const playerIds = players.filter(player => currentGame.playerIds?.includes(player.id)) + const playerIds = game.playerIds .sort((a, b) => { - if (currentGame?.playerIds == undefined) return 0; - return currentGame.playerIds.indexOf(a.id) - currentGame.playerIds.indexOf(b.id); + if (game?.playerIds == undefined) return 0; + return game.playerIds.indexOf(a) - game.playerIds.indexOf(b); }) - .map(player => player.id); + .map(player => player); - if (SortDirectionKey.Normal === currentGame.sortDirectionKey) { + if (SortDirectionKey.Normal === sortDirectionKey) { return playerIds; } else { return playerIds.reverse(); @@ -28,7 +27,7 @@ export const selectPlayerIdsByIndex: SortSelector = createSelector( } ); -export const selectPlayerIdsByScore: SortSelector = createSelector( +export const selectSortedPlayerIdsByScore: SortSelector = createSelector( [ selectAllPlayers, (state: RootState) => state.settings.currentGameId ? state.games.entities[state.settings.currentGameId] : undefined, @@ -78,6 +77,6 @@ export enum SortDirectionKey { } export const sortSelectors = { - [SortSelectorKey.ByScore]: selectPlayerIdsByScore, - [SortSelectorKey.ByIndex]: selectPlayerIdsByIndex, + [SortSelectorKey.ByScore]: selectSortedPlayerIdsByScore, + [SortSelectorKey.ByIndex]: selectSortedPlayerIdsByIndex, }; diff --git a/src/screens/SettingsScreen.tsx b/src/screens/SettingsScreen.tsx index 0a1edba6..6464aeb2 100644 --- a/src/screens/SettingsScreen.tsx +++ b/src/screens/SettingsScreen.tsx @@ -14,7 +14,7 @@ import { playerAdd } from '../../redux/PlayersSlice'; import { selectCurrentGame } from '../../redux/selectors'; import EditGame from '../components/EditGame'; import PlayerListItem from '../components/PlayerListItem'; -import { selectPlayerIdsByIndex } from '../components/ScoreLog/SortHelper'; +import { selectSortedPlayerIdsByIndex } from '../components/ScoreLog/SortHelper'; import { MAX_PLAYERS, systemBlue } from '../constants'; import logger from '../Logger'; @@ -36,7 +36,7 @@ const SettingsScreen: React.FunctionComponent = ({ navigation }) => { if (typeof currentGameId == 'undefined') return null; const currentGame = useAppSelector(selectCurrentGame); - const playerIds = useAppSelector(selectPlayerIdsByIndex); + const playerIds = useAppSelector(selectSortedPlayerIdsByIndex); const [edit, setEdit] = React.useState(false); From 28f6f6cf02bbecfae23028445105b6ed9d39c7eb Mon Sep 17 00:00:00 2001 From: Justin Wyne <1986068+wyne@users.noreply.github.com> Date: Sun, 21 Apr 2024 15:34:45 -0700 Subject: [PATCH 5/7] cleanup --- src/components/ScoreLog/RoundScoreColumn.tsx | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/src/components/ScoreLog/RoundScoreColumn.tsx b/src/components/ScoreLog/RoundScoreColumn.tsx index ae588f05..2a696e34 100644 --- a/src/components/ScoreLog/RoundScoreColumn.tsx +++ b/src/components/ScoreLog/RoundScoreColumn.tsx @@ -3,11 +3,12 @@ import React, { memo, useCallback } from 'react'; import analytics from '@react-native-firebase/analytics'; import { LayoutChangeEvent, Text, TouchableWithoutFeedback, View } from 'react-native'; -import { selectSortSelectorKey, updateGame } from '../../../redux/GamesSlice'; +import { updateGame } from '../../../redux/GamesSlice'; import { useAppDispatch, useAppSelector } from '../../../redux/hooks'; +import { selectCurrentGame } from '../../../redux/selectors'; import RoundScoreCell from './RoundScoreCell'; -import { sortSelectors } from './SortHelper'; +import { SortSelectorKey, sortSelectors } from './SortHelper'; interface Props { round: number; @@ -24,11 +25,9 @@ const RoundScoreColumn: React.FunctionComponent = ({ }) => { const dispatch = useAppDispatch(); - const currentGameId = useAppSelector(state => state.settings.currentGameId); - - const sortSelectorKey = useAppSelector(state => selectSortSelectorKey(state, currentGameId || '')); - const sortSelector = sortSelectors[sortSelectorKey]; - + const currentGameId = useAppSelector(state => selectCurrentGame(state)?.id); + const sortKey = useAppSelector(state => selectCurrentGame(state)?.sortSelectorKey); + const sortSelector = sortSelectors[sortKey || SortSelectorKey.ByIndex]; const sortedPlayerIds = useAppSelector(sortSelector); const onPressHandler = useCallback(async () => { From 49d035b29925d3ef7fb6394cc4976955542c7753 Mon Sep 17 00:00:00 2001 From: Justin Wyne <1986068+wyne@users.noreply.github.com> Date: Sun, 21 Apr 2024 15:45:56 -0700 Subject: [PATCH 6/7] Fix readonly redux bug --- src/components/ScoreLog/SortHelper.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/ScoreLog/SortHelper.ts b/src/components/ScoreLog/SortHelper.ts index 260c92ff..bed4a3f9 100644 --- a/src/components/ScoreLog/SortHelper.ts +++ b/src/components/ScoreLog/SortHelper.ts @@ -12,7 +12,7 @@ export const selectSortedPlayerIdsByIndex = createSelector( (game: GameState | undefined, sortDirectionKey: SortDirectionKey | undefined) => { if (!game) return []; - const playerIds = game.playerIds + const playerIds = [...game.playerIds] .sort((a, b) => { if (game?.playerIds == undefined) return 0; return game.playerIds.indexOf(a) - game.playerIds.indexOf(b); From b4665d07f29e517e0a81d99d907bea889e64ecbb Mon Sep 17 00:00:00 2001 From: Justin Wyne <1986068+wyne@users.noreply.github.com> Date: Sun, 5 May 2024 21:19:30 -0700 Subject: [PATCH 7/7] Correct playerColor selectors to prevent unnecessary renders --- redux/GamesSlice.ts | 28 +++++++++++++------- src/components/Boards/FlexboxTile.tsx | 8 +++--- src/components/PlayerListItem.tsx | 5 ++-- src/components/ScoreLog/PlayerNameColumn.tsx | 6 +++-- 4 files changed, 30 insertions(+), 17 deletions(-) diff --git a/redux/GamesSlice.ts b/redux/GamesSlice.ts index f59096b4..dc902159 100644 --- a/redux/GamesSlice.ts +++ b/redux/GamesSlice.ts @@ -270,20 +270,27 @@ export const selectSortSelectorKey = (state: RootState, gameId: string) => { return key !== undefined ? key : SortSelectorKey.ByScore; }; -export const selectPlayerColors = createSelector( +export const makeSelectPlayerColors = () => createSelector( [ - (state: RootState, playerId: string) => { - const gameId = selectAllGames(state).filter((game) => game.playerIds.includes(playerId))[0].id; - const paletteName = state.games.entities[gameId]?.palette; + (state: RootState, gameId: string | undefined): GameState | undefined => { + if (!gameId) { + return undefined; + } + return state.games.entities[gameId]; + }, + (state: RootState, gameId: string | undefined, playerId: string): string => playerId, + (state: RootState, gameId: string | undefined, playerId: string): string | undefined => state.players.entities[playerId]?.color, + ], + (game, playerId, playerColor) => { + if (!game || !playerId) { + return ['#000000', '#FFFFFF']; + } - const playerColor = state.players.entities[playerId]?.color; + const paletteName = game.palette; + const playerIds = game.playerIds; - const playerIndex = state.games.entities[gameId]?.playerIds.indexOf(playerId) || 0; + const playerIndex = playerIds.indexOf(playerId); - return { paletteName, playerColor, playerIndex }; - }, - ], - ({ paletteName, playerColor, playerIndex }) => { const palette = getPalette(paletteName || 'original') || getPalette('original'); const paletteBG = palette[playerIndex % palette.length]; @@ -299,6 +306,7 @@ export const selectPlayerColors = createSelector( } ); + export const { updateGame, roundNext, diff --git a/src/components/Boards/FlexboxTile.tsx b/src/components/Boards/FlexboxTile.tsx index 54fc9d03..fe14be57 100644 --- a/src/components/Boards/FlexboxTile.tsx +++ b/src/components/Boards/FlexboxTile.tsx @@ -3,9 +3,9 @@ import React from 'react'; import { DimensionValue, StyleSheet } from 'react-native'; import Animated, { Easing, FadeIn } from 'react-native-reanimated'; -import { selectPlayerColors } from '../../../redux/GamesSlice'; +import { makeSelectPlayerColors } from '../../../redux/GamesSlice'; import { useAppSelector } from '../../../redux/hooks'; -import { selectInteractionType } from '../../../redux/selectors'; +import { selectCurrentGame, selectInteractionType } from '../../../redux/selectors'; import { interactionComponents } from '../Interactions/InteractionComponents'; import { InteractionType } from '../Interactions/InteractionType'; import AdditionTile from '../PlayerTiles/AdditionTile/AdditionTile'; @@ -32,8 +32,10 @@ const FlexboxTile: React.FunctionComponent = React.memo(({ if (!(width > 0 && height > 0)) return null; if (Number.isNaN(width) || Number.isNaN(height)) return null; + const currentGameId = useAppSelector(state => selectCurrentGame(state)?.id); const playerIndexLabel = useAppSelector(state => state.settings.showPlayerIndex); - const playerColors = useAppSelector(state => selectPlayerColors(state, playerId)); + const selectPlayerColors = makeSelectPlayerColors(); + const playerColors = useAppSelector(state => selectPlayerColors(state, currentGameId, playerId)); const [bg, fg] = playerColors; const widthPerc: DimensionValue = `${(100 / cols)}%`; diff --git a/src/components/PlayerListItem.tsx b/src/components/PlayerListItem.tsx index 581ca685..931fab8e 100644 --- a/src/components/PlayerListItem.tsx +++ b/src/components/PlayerListItem.tsx @@ -6,7 +6,7 @@ import { NativeStackNavigationProp } from '@react-navigation/native-stack'; import { Alert, StyleSheet, Text, TouchableOpacity, View } from 'react-native'; import { Icon, ListItem } from 'react-native-elements'; -import { selectGameById, selectPlayerColors, updateGame } from '../../redux/GamesSlice'; +import { makeSelectPlayerColors, selectGameById, updateGame } from '../../redux/GamesSlice'; import { useAppDispatch, useAppSelector } from '../../redux/hooks'; import { removePlayer, selectPlayerById } from '../../redux/PlayersSlice'; import { selectCurrentGame } from '../../redux/selectors'; @@ -31,7 +31,8 @@ const PlayerListItem: React.FunctionComponent = ({ const currentGameId = useAppSelector(state => selectCurrentGame(state)?.id); const playerIds = useAppSelector(state => selectGameById(state, currentGameId || '')?.playerIds); const player = useAppSelector(state => selectPlayerById(state, playerId)); - const playerColors = useAppSelector(state => selectPlayerColors(state, playerId)); + const selectPlayerColors = makeSelectPlayerColors(); + const playerColors = useAppSelector(state => selectPlayerColors(state, currentGameId, playerId)); if (currentGameId == '' || currentGameId === undefined) return null; if (playerIds === undefined) return null; diff --git a/src/components/ScoreLog/PlayerNameColumn.tsx b/src/components/ScoreLog/PlayerNameColumn.tsx index a71bcd3c..e6785d06 100644 --- a/src/components/ScoreLog/PlayerNameColumn.tsx +++ b/src/components/ScoreLog/PlayerNameColumn.tsx @@ -3,7 +3,7 @@ import React from 'react'; import { StyleSheet, Text, View } from 'react-native'; import { Icon } from 'react-native-elements/dist/icons/Icon'; -import { selectPlayerColors } from '../../../redux/GamesSlice'; +import { makeSelectPlayerColors } from '../../../redux/GamesSlice'; import { useAppSelector } from '../../../redux/hooks'; import { selectPlayerById } from '../../../redux/PlayersSlice'; import { selectCurrentGame } from '../../../redux/selectors'; @@ -17,7 +17,9 @@ interface CellProps { } const PlayerNameCell: React.FunctionComponent = ({ index, playerId }) => { - const playerColors = useAppSelector(state => selectPlayerColors(state, playerId)); + const selectPlayerColors = makeSelectPlayerColors(); + const currentGameId = useAppSelector(state => selectCurrentGame(state)?.id); + const playerColors = useAppSelector(state => selectPlayerColors(state, currentGameId, playerId)); const playerName = useAppSelector(state => selectPlayerById(state, playerId)?.playerName); return (