function component for state component - reactjs

I would like to rewrite the function component into a state component. I have a problem with useCallback. Please help.
component function:
const App = () => {
const [elements, setElements] = useState([]);
const [history, setHistory] = useState([]);
const [currentHistoryIndex, setCurrentHistoryIndex] = useState(0);
const handleOnHistoryPush = useCallback(() => {
setHistory([...history, elements]);
setElements([]);
}, [elements, history]);
const appendElement = useCallback(
value => setElements([...elements, value]),
[elements]
);
class component:
state = {
elements: [],
history: [],
currentHistoryIndex: 0
}
handleOnHistoryPush = useCallback(() => {
this.setState({history: ([...this.state.history, this.state.elements])});
this.setState({elements:([])});
}, [this.state.elements, this.state.history]);
appendElement = useCallback(
value => this.setState({elements:([...this.state.elements, value])}),
[this.state.elements]
);

this is a correct way to use state components callback function
state = {
elements: [],
history: [],
currentHistoryIndex: 0
}
handleOnHistoryPush = () => {
this.setState({history: ([...this.state.history, this.state.elements])});
this.setState({elements:([])});
};
appendElement = value => this.setState({elements:([...this.state.elements, value])});

Related

How to solve a situation when a component calls setState inside useEffect but the dependencies changes on every render?

I have this component:
const updateUrl = (url: string) => history.replaceState(null, '', url);
// TODO: Rename this one to account transactions ATT: #dmuneras
const AccountStatement: FC = () => {
const location = useLocation();
const navigate = useNavigate();
const { virtual_account_number: accountNumber, '*': transactionPath } =
useParams();
const [pagination, setPagination] = useState<PaginatorProps>();
const [goingToInvidualTransaction, setGoingToInvidualTransaction] =
useState<boolean>(false);
const SINGLE_TRANSACTION_PATH_PREFIX = 'transactions/';
// TODO: This one feels fragile, just respecting what I found, but, we could
// investigate if we can jsut rely on the normal routing. ATT. #dmuneras
const transactionId = transactionPath?.replace(
SINGLE_TRANSACTION_PATH_PREFIX,
''
);
const isFirst = useIsFirstRender();
useEffect(() => {
setGoingToInvidualTransaction(!!transactionId);
}, [isFirst]);
const {
state,
queryParams,
dispatch,
reset,
setCursorAfter,
setCursorBefore
} = useLocalState({
cursorAfter: transactionId,
includeCursor: !!transactionId
});
const {
filters,
queryParams: globalQueryParams,
setDateRange
} = useGlobalFilters();
useUpdateEffect(() => {
updateUrl(
`${location.pathname}?${prepareSearchParams(location.search, {
...queryParams,
...globalQueryParams
}).toString()}`
);
}, [transactionId, queryParams]);
useUpdateEffect(() => dispatch(reset()), [globalQueryParams]);
const account_number = accountNumber;
const requestParams = accountsStateToParams({
account_number,
...state,
...filters
});
const { data, isFetching, error, isSuccess } =
useFetchAccountStatementQuery(requestParams);
const virtualAccountTransactions = data && data.data ? data.data : [];
const nextPage = () => {
dispatch(setCursorAfter(data.meta.cursor_next));
};
const prevPage = () => {
dispatch(setCursorBefore(data.meta.cursor_prev));
};
const onRowClick = (_event: React.MouseEvent<HTMLElement>, rowData: any) => {
if (rowData.reference) {
if (rowData.id == transactionId) {
navigate('.');
} else {
const queryParams = prepareSearchParams('', {
reference: rowData.reference,
type: rowData.entry_type,
...globalQueryParams
});
navigate(
`${SINGLE_TRANSACTION_PATH_PREFIX}${rowData.id}?${queryParams}`
);
}
}
};
const checkIfDisabled = (rowData: TransactionData): boolean => {
return !rowData.reference;
};
useEffect(() => {
if (data?.meta) {
setPagination({
showPrev: data.meta.has_previous_page,
showNext: data.meta.has_next_page
});
}
}, [data?.meta]);
const showTransactionsTable: boolean =
Array.isArray(virtualAccountTransactions) && isSuccess && data?.data;
const onTransactionSourceLoaded = (
transactionSourceData: PayoutDetailData
) => {
const isIncludedInPage: boolean = virtualAccountTransactions.some(
(transaction: TransactionData) => {
if (transactionId) {
return transaction.id === parseInt(transactionId, 10);
}
return false;
}
);
if (!goingToInvidualTransaction || isIncludedInPage) {
return;
}
const fromDate = dayjs(transactionSourceData.timestamp);
const toDate = fromDate.clone().add(30, 'day');
setDateRange({
type: 'custom',
to: toDate.format(dateFormat),
from: fromDate.format(dateFormat)
});
setGoingToInvidualTransaction(false);
};
const fromDate = requestParams.created_after || dayjs().format('YYYY-MM-DD');
const toDate = requestParams.created_before || dayjs().format('YYYY-MM-DD');
const routes = [
{
index: true,
element: (
<BalanceWidget
virtualAccountNumber={account_number}
fromDate={fromDate}
toDate={toDate}
/>
)
},
{
path: `${SINGLE_TRANSACTION_PATH_PREFIX}:transaction_id`,
element: (
<TransactionDetails
onTransactionSourceLoaded={onTransactionSourceLoaded}
/>
)
}
];
return (........
I get this error: Warning: Maximum update depth exceeded. This can happen when a component calls setState inside useEffect, but useEffect either doesn't have a dependency array, or one of the dependencies changes on every render.
The useEffect where the issue is, it is this one:
useEffect(() => {
if (data?.meta) {
setPagination({
showPrev: data.meta.has_previous_page,
showNext: data.meta.has_next_page
});
}
}, [data?.meta]);
Considering previous answers, would the solution be to make sure I return a new object each time? But I am not sure what would be the best approach. Any clues ?
did you want the useEffect to start every changes of 'data?.meta' ?
Without reading all the code, I believe the data.meta object changes on every render. There is a way to change the useEffect to narrow done its execution conditions:
useEffect(() => {
if (data?.meta) {
setPagination({
showPrev: data.meta.has_previous_page,
showNext: data.meta.has_next_page
});
}
}, [!data?.meta, data?.meta?.has_previous_page, data?.meta?.has_next_page]);
Please note the ! before data.?.meta which makes the hook test only for presence or absence of the object, since your code doesn't need more than that information.

What is this react functional component which returns functions instead of template?

Please can someone tell me what is such as react component that returns a bunch of functions and which uses react hooks inside of it ?
Even a react functional component is supposed to return jsx or any other template , no ?
Is such as component stated in react documentation ? how is it called ?!
export default function useViewFilter() {
const dispatch = useDispatch();
const { boardId, viewId } = useParams<RouteParamsTypes>();
const [selectedFacets, setSelectedFacets] = useState<SelectedFacets>({});
const [propertiesData, setPropertiesData] = useState<PropertyData[]>([]);
const [propertiesFacetingTypes, setPropertiesFacetingTypes] = useState<PropertiesFacetingTypes>({});
const neededData = useSelector((state: IFrontAppState) => selectFilterHookNeededData(state, { boardId, viewId }));
const isReady = useSelector((state: IFrontAppState) => isViewModelLoaded(state, boardId, viewId));
const storeFacetsFilters = useSelector((state: IFrontAppState) => getViewFacetsFilters(state, boardId, viewId));
const storeFacetingTypes = useSelector((state: IFrontAppState) =>
getViewPropertiesFacetingTypes(state, boardId, viewId)
);
const dataRef = useRef({
initialized: false
});
useEffect(() => {
handleMakeNewSearch(selectedFacets, false);
}, [neededData]);
// last state before refresh
useEffect(() => {
if (isReady && !dataRef.current.initialized) {
dataRef.current.initialized = true;
setSelectedFacets(storeFacetsFilters ? (storeFacetsFilters as SelectedFacets) : {});
setPropertiesFacetingTypes(storeFacetingTypes ? storeFacetingTypes : {});
handleMakeNewSearch(storeFacetsFilters as SelectedFacets, false);
}
}, [isReady, storeFacetsFilters, storeFacetingTypes, dataRef]);
const handleClear = () => {
const selectedFacets = {};
setSelectedFacets(selectedFacets);
handleMakeNewSearch(selectedFacets, true);
dispatch(
actions.ClearViewFilters({
BoardId: boardId,
ViewId: viewId
})
);
};
const handleMakeNewSearch = (selectedFacets: SelectedFacets, doUpdateInBackend: boolean) => {
const { propertiesIds, properties, orderedEntities, boardMembersObj } = neededData;
if (properties && propertiesIds?.length) {
const orderedProperties = propertiesIds?.map(pId => ({
Id: pId,
...properties[pId]
}));
const itemsJsData = makeItemsJsDataFromAllEntities(orderedEntities, orderedProperties, boardMembersObj);
const newSearchResult = makeSearchAggregations(itemsJsData, orderedProperties, selectedFacets);
// set filtred items
if (doUpdateInBackend) {
const filtredEntitiesIds = newSearchResult.items.map(x => x.id) as string[];
dispatch(
actions.UpdateViewFilters({
BoardId: boardId,
ViewId: viewId,
FacetsFilters: selectedFacets,
FiltredEntitiesIds: doesThereIsFeltering(selectedFacets) ? filtredEntitiesIds : null,
PropertiesFacetingTypes: propertiesFacetingTypes
})
);
}
// update filter panel
const newPropertiesData = makeNewPropertiesData(
propertiesIds,
properties,
newSearchResult.aggregations,
propertiesFacetingTypes
);
setPropertiesData(newPropertiesData);
}
};
return {
propertiesData,
propertiesFacetingTypes,
handleClear,
setPropertiesFacetingTypes
};
}
It's a custom hook.
it's one of the ways (nowadays maybe the standard way) of extracting logic in React function components.

Why is my property always wrong, even though it gets changed and is a dependency?

I have these properties declared in my app:
const [lockfileData, setLockFileData] = useState({});
const [socket, setSocket] = useState<RiotWSProtocol>(null);
const [api, setApi] = useState<LoLAPI>(null);
const [champions, setChampions] = useState<Champion[]>([]);
const [summoner, setSummoner] = useState<Summoner>(null);
const [autoAcceptQueue, setAutoAccept] = useState(true);
const [instalockEnabled, setEnableInstalock] = useState(true);
const [selectedChampion, setSelectedChampion] = useState<Champion>(null);
const [callRoleEnabled, setCallRoleEnabled] = useState(true);
const [selectedRole, setSelectedRole] = useState<Role>('Mid');
I have an event handler in my useEffect hook, and inside that it handles more events:
const onJsonApiEvent = useCallback(
(message: any) => {
//console.log(message);
if (
message.uri === '/lol-matchmaking/v1/ready-check' &&
autoAcceptQueue
) {
if (
message.data?.state === 'InProgress' &&
message.data?.playerResponse !== 'Accepted'
) {
api.acceptQueue();
}
} else if (
message.uri === '/lol-champ-select/v1/session' &&
message.eventType === 'Update'
) {
console.log('enabled?', instalockEnabled)
if (instalockEnabled) {
const myCellId = message.data.localPlayerCellId as number;
const myAction = (message.data.actions[0] as any[]).find(
(x) => x.actorCellId === myCellId
);
if (
!myAction.completed &&
myAction.isInProgress &&
myAction.type === 'pick'
) {
api.pickAndLockChampion(1, myAction.id);
}
console.log('myAction', myAction);
}
}
},
[api, autoAcceptQueue, instalockEnabled]
);
const onSocketOpen = useCallback(() => {
console.log('socket', socket);
if (socket) {
socket.subscribe('OnJsonApiEvent', onJsonApiEvent);
}
}, [onJsonApiEvent, socket]);
const onConnect = useCallback((data: LCUCredentials) => {
setLockFileData(data);
const lolApi = new LoLAPI(data);
setApi(lolApi);
lolApi.getOwnedChampions().then((champs) => {
setSelectedChampion(champs[0]);
setChampions(champs);
});
lolApi.getCurrentSummoner().then((summoner) => {
setSummoner(summoner);
});
const wss = new RiotWSProtocol(
`wss://${data.username}:${data.password}#${data.host}:${data.port}`
);
setSocket(wss);
}, []);
useEffect(() => {
if (socket) {
socket.on('open', onSocketOpen);
}
connector.on('connect', onConnect);
connector.start();
return () => {
connector.stop();
};
}, [onConnect, onSocketOpen, socket]);
The dependencies appear to be correct, so it should be using the up to date values in each handler.
However, inside the onJsonApiEvent handler, properties such as instalockEnabled are always the default value.
I am updating the value of instalockEnabled in a component on my page:
<FormControlLabel
control={
<Checkbox
checked={instalockEnabled}
name="instalockEnabled"
color="primary"
onChange={handleInstalockEnableChange}
/>
}
label="Enabled"
/>
And its handler looks like this:
const handleInstalockEnableChange = (
e: React.ChangeEvent<HTMLInputElement>
) => {
setEnableInstalock(e.target.checked);
};
How come this is happening when it is a dependency?
The janky solution I've come up with for now is to have a separate variable that is useRef and update that at the same time as updating the state, therefore it persists:
const [instalockEnabled, setEnableInstalock] = useState(true);
const instalockEnabledRef = useRef(instalockEnabled);
const handleInstalockEnableChange = (
e: React.ChangeEvent<HTMLInputElement>
) => {
setEnableInstalock(e.target.checked);
instalockEnabledRef.current = e.target.checked;
};
And then just use instalockEnabledRef.current inside of the event handlers where it needs to know the current value.

prevent rerender for every setstate using react usestate hook

I have some multiple states and functions to set the state using useState hook.
on each setstate component gets re-render. I want to re-render only functions of setstate fully completed.
for example:
in app.js
const [a, setA] = useState(() => false);
const [b, setB] = useState(() => {});
const [c, setC] = useState(() => props.c);
const [d, setD] = useState(null);
const [e, setE] = useState(null);
const [f, setF] = useState(null);
const [g, setG] = useState('');
const func1 = useCallback(data => {
setA(true);
setB(true);
setC(data && data.c);
setD();
setE(isChecked ? 'a' : 'b');
}, []);
const func2 = useCallback(data => {
setA(false);
setB(false);
}, []);
const func3 = useCallback(data => {
setC(false);
setD(false);
}, []);
const events = {
func1,
func2,
func3
};
const handleMessage = e => {
const { eventName, data } = e.data;
const handleEvents = eventName ? events[toCamel(eventName)] : null;
if (handleEvents && typeof handleEvents === 'function') {
handleEvents(data);
}
};
useLayoutEffect(() => {
iframe.addEventListener('message', handleMessage, true);
return () => {
iframe.removeEventListener('message', handleMessage, true);
};
}, []);
return (
<Fragment>
{a && (
Component1
)}
{b && c && (
Component2
)}
</Fragment>
);
On each func1 and func2 setA and setB return statement rerender the element. I don't want to re-render on each setA,setB,setC etc. once func1 or func2 fully completes only want rerender the components. As am new to hooks, can someone help with this?
if your state values are so deeply connected and you want to make sure everything is updated at once:
const [state, setState] = useState({
a: () => false,
b: () => {},
c: () => props.c,
d: null,
e: null,
f: null,
g: ''
});
const updateState = newState => setState(Object.assign({}, state, newState));
now when you want to update your state just use updateState like this:
const func1 = useCallback(
(data) =>
updateState({
a: true,
b: true,
c: data && data.c,
d: undefined,
e: isChecked ? 'a' : 'b',
}),
[],
);
you can store all your variable in one state like this:
const [state,setState] = useState({ a:false, b:{}, c:props.c,d:null})
const func1 = useCallback(data => {
setstate({a:true,b:true,c:data && data.c,d:null})
}, []);

How to filter array with Hooks?

I am currently working on a class-based function. Trying to convert the class to a stateless function, followed by refactoring my code for each event handler from this.SetState to useState (in this case setMovies).
This is my code (partial code):
const Movies = () => {
const [movies, setMovies] = useState(getMovies());
const [disabled, setDisabled] = useState(true);
const [pageSize, setPageSize] = useState(4);
const sortBy = sortType => {
setMovies(movies.filter(sortType));
setDisabled(false);
// this.setState({
// movies: this.state.movies.sort(sortType),
// isDisabled: false,
// });
};
it seems that It is not possible to filter this state. I am able to change to boolean but can't filter my Array. Is there a way to filter using Hooks?
Thanks in advance.
Nothing changes...
const List = () => {
const [items, setItems] = useState([1, 2, 3, 4, 5, 6])
const filterEvenResults = () => setItems(items => items.filter(x => x % 2))
return (
<div>
{
items.map(item => <p key={item}>{item}</p>)
}
<button onClick={filterEvenResults}>Filter</button>
</div>
)
}

Resources