I'm trying to pass an Array of Objects(Enums) from a son component to the father. But I'm quite not getting the trick.
Father Component:
const [openDialog, setOpenDialog] = useState(false);
const handleReturn = () => filtros;
const [filtros, setFiltros] = useState<Filtros[]>([]);
useEffect(() => {
setFiltros(handleReturn);
console.log(filtros);
}, [openDialog]);
<FiltrosDialog
openDialog={openDialog}
handleCloseDialog={() => setOpenDialog(false)}
filtrosSelec={filtros}
/>
Son Component (there's a lot of code here but trust me, the state is returning an Array of Filtros):
interface Props {
openDialog: boolean;
handleCloseDialog: () => void;
filtrosSelec: Filtros[];
}
export enum Filtros {
Ligacoes = "Ligações",
Tempo = "Tempo em chamada (min)",
Email = "E-mails enviados",
Reuniao = "Reuniões agendadas",
}
const FiltrosDialog: React.FunctionComponent<Props> = ({
openDialog,
handleCloseDialog,
filtrosSelec,
})=> {
const [checked, setChecked] = useState<Filtros[]>([]);
const [left, setLeft] = useState<Filtros[]>([
Filtros.Reuniao,
Filtros.Tempo,
Filtros.Email,
Filtros.Ligacoes,
]);
const [right, setRight] = useState<Filtros[]>(filtrosSelec);
const leftChecked = intersecao(checked, left);
const rightChecked = intersecao(checked, right);
...}
Basically, I'm not sure if my Filtros[] props is supossed to be a function or an array itself...
Your states are fine, but here's the logic for child/parent communication in terms of asking the child to change parent property.
cont Parent = () => {
const [count, setCount] = useState(0)
return <Child count={count} change={setCount} />
}
You can see the setCount is passed it, not count. A child can only change parent property through a method. Because in terms of setting the property, count = 3 is not going to make it due to that count is a primitive value.
This is normally quite clear when you are creating an input component, such as the following.
<Input value={value} onChange={onChange} />
Just found out how to do it. I need to pass the setState as the function inside the child prop on the parent component. And the child prop need to be a void and its argument is the actual Array:
Parent:
const [filtros, setFiltros] = useState<Filtros[]>([]);
...
<FiltrosDialog
openDialog={openDialog}
handleCloseDialog={() => setOpenDialog(false)}
filtrosSelec={setFiltros} />
Child:
interface Props {
openDialog: boolean;
handleCloseDialog: () => void;
filtrosSelec: (arg: Filtros[]) => void;
}
const FiltrosDialog: React.FunctionComponent<Props> = ({
openDialog,
handleCloseDialog,
filtrosSelec,
}) => {
const [right, setRight] = useState<Filtros[]>([]);
useEffect(() => filtrosSelec(right));
...
I have the following code in my App.jsx:
render() {
return (
<BrowserView>
<CreateSession /> // works just fine
<QrCode address={CreateSession(this)} /> // throws 'Error: Invalid hook call.'
</BrowserView>)
}
CreateSession returns a string, which is fed into QrCode, to generate a Qr Code. My CreateSession looks like this:
const CreateSession = (props) => {
const userVideo = useRef();
const partnerVideo = useRef();
const peerRef = useRef();
const socketRef = useRef();
const otherUser = useRef();
const userStream = useRef();
useEffect(() => {
socketRef.current = io.connect("/");
socketRef.current.emit("join session", props.match.params.roomID);
// lots of code omitted, source is: https://github.com/coding-with-chaim/native-webrtc/blob/master/client/src/routes/Room.js
return uuid();
};
export default CreateSession;
What is the correct way to call CreateSession so that it returns the uuid right into QrCode? I am aware that I could have a state property in the App.jsx that gets set to uuid, that is then passed into QrCode, but is it possible to do it this way?
You can turn your CreateSession component into a wrapper.
const CreateSession = (props) => {
const userVideo = useRef();
const partnerVideo = useRef();
const peerRef = useRef();
const socketRef = useRef();
const otherUser = useRef();
const userStream = useRef();
const [uuid, setUuid] = useState(null);
useEffect(() => {
socketRef.current = io.connect("/");
socketRef.current.emit("join session", props.match.params.roomID);
// lots of code omitted, source is: https://github.com/coding-with-chaim/native-webrtc/blob/master/client/src/routes/Room.js
setUuid(uuid());
});
if (uuid === null) {
return null;
}
return (<>{props.children(uuid)}</>)
};
export default CreateSession;
Here is the usage.
render() {
return (
<BrowserView>
<CreateSession>
{(uuid) => (<QrCode address={uuid} />)}
</CreateSession>
</BrowserView>
)
}
The class methods which are passed as args from the functional component, are kept 'in memory' and doest not reflect the updated state. I can reinitialise on state changes but wish to avoid it.
const MyFunctional = (props) => {
const [state,setState] = useState(0);
const helper = useRef();
useEffect(()=>{
helper.current = new HelperClass(onSuccess,onFailure);
},[])
/* wish to avoid */
useEffect(()=>{
helper.current = new HelperClass(onSuccess,onFailure);
},[state])
const onSuccess = (result) =>{
/* Here state == 0 */
}
const onFailure = (error) =>{
/* Here state == 0 */
}
}
You'll need an additional ref to be able to use the latest values in an async callback.
Either
grab react-use's useLatest hook,
write one yourself according to the docs,
or steal this trivial reference implementation:
function useLatest(value) {
const ref = useRef(value);
ref.current = value;
return ref;
};
const MyFunctional = (props) => {
const [state, setState] = useState(0);
const latestStateRef = useLatest(state);
const helper = useRef();
useEffect(() => {
helper.current = new HelperClass(onSuccess, onFailure);
}, []);
const onSuccess = (result) => {
console.log(latestStateRef.current);
};
const onFailure = (error) => {
console.log(latestStateRef.current);
};
};
This is my code.
const initialNoticeState = {
id: props,
title : '',
description : '',
image : '',
updated_by : user.firstName+' '+user.lastName,
priority : ''
};
const [currentNotice, setCurrentNotice] = useState(initialNoticeState);
const notice = useSelector(state => state.notices.currentNotice)
const [noticeImage, setNoticeImage] = useState("");
const [imgData, setImgData] = useState(null);
const dispatch = useDispatch();
useEffect(() => {
dispatch(noticeActions.getById(props.id))
}, [props.id]);
I want to setCurrentNotice from notice value, just after dispatch finishes.
Here is the data of notice:
I want to setCurrentNotice from notice value, just after dispatch
finishes.
useEffect can be used to execute functions when variable is changed.
useEffect(() => {
setCurrentNotice(notice)
}, [notice]); //notice is the dependency
Just Like that
setCurrentNotice({your current notice data})
I have something like:
const [loading, setLoading] = useState(false);
...
setLoading(true);
doSomething(); // <--- when here, loading is still false.
Setting state is still async, so what's the best way to wait for this setLoading() call to be finished?
The setLoading() doesn't seem to accept a callback like setState() used to.
an example
class-based
getNextPage = () => {
// This will scroll back to the top, and also trigger the prefetch for the next page on the way up.
goToTop();
if (this.state.pagesSeen.includes(this.state.page + 1)) {
return this.setState({
page: this.state.page + 1,
});
}
if (this.state.prefetchedOrders) {
const allOrders = this.state.orders.concat(this.state.prefetchedOrders);
return this.setState({
orders: allOrders,
page: this.state.page + 1,
pagesSeen: [...this.state.pagesSeen, this.state.page + 1],
prefetchedOrders: null,
});
}
this.setState(
{
isLoading: true,
},
() => {
getOrders({
page: this.state.page + 1,
query: this.state.query,
held: this.state.holdMode,
statuses: filterMap[this.state.filterBy],
})
.then((o) => {
const { orders } = o.data;
const allOrders = this.state.orders.concat(orders);
this.setState({
orders: allOrders,
isLoading: false,
page: this.state.page + 1,
pagesSeen: [...this.state.pagesSeen, this.state.page + 1],
// Just in case we're in the middle of a prefetch.
prefetchedOrders: null,
});
})
.catch(e => console.error(e.message));
},
);
};
convert to function-based
const getNextPage = () => {
// This will scroll back to the top, and also trigger the prefetch for the next page on the way up.
goToTop();
if (pagesSeen.includes(page + 1)) {
return setPage(page + 1);
}
if (prefetchedOrders) {
const allOrders = orders.concat(prefetchedOrders);
setOrders(allOrders);
setPage(page + 1);
setPagesSeen([...pagesSeen, page + 1]);
setPrefetchedOrders(null);
return;
}
setIsLoading(true);
getOrders({
page: page + 1,
query: localQuery,
held: localHoldMode,
statuses: filterMap[filterBy],
})
.then((o) => {
const { orders: fetchedOrders } = o.data;
const allOrders = orders.concat(fetchedOrders);
setOrders(allOrders);
setPage(page + 1);
setPagesSeen([...pagesSeen, page + 1]);
setPrefetchedOrders(null);
setIsLoading(false);
})
.catch(e => console.error(e.message));
};
In the above, we want to run each setWhatever call sequentially. Does this mean we need to set up many different useEffect hooks to replicate this behavior?
useState setter doesn't provide a callback after state update is done like setState does in React class components. In order to replicate the same behaviour, you can make use of the a similar pattern like componentDidUpdate lifecycle method in React class components with useEffect using Hooks
useEffect hooks takes the second parameter as an array of values which React needs to monitor for change after the render cycle is complete.
const [loading, setLoading] = useState(false);
...
useEffect(() => {
doSomething(); // This is be executed when `loading` state changes
}, [loading])
setLoading(true);
EDIT
Unlike setState, the updater for useState hook doesn't have a callback, but you can always use a useEffect to replicate the above behaviour. However you need to determine the loading change
The functional approach to your code would look like
function usePrevious(value) {
const ref = useRef();
useEffect(() => {
ref.current = value;
});
return ref.current;
}
const prevLoading = usePrevious(isLoading);
useEffect(() => {
if (!prevLoading && isLoading) {
getOrders({
page: page + 1,
query: localQuery,
held: localHoldMode,
statuses: filterMap[filterBy],
})
.then((o) => {
const { orders: fetchedOrders } = o.data;
const allOrders = orders.concat(fetchedOrders);
setOrders(allOrders);
setPage(page + 1);
setPagesSeen([...pagesSeen, page + 1]);
setPrefetchedOrders(null);
setIsLoading(false);
})
.catch(e => console.error(e.message));
}
}, [isLoading, preFetchedOrders, orders, page, pagesSeen]);
const getNextPage = () => {
// This will scroll back to the top, and also trigger the prefetch for the next page on the way up.
goToTop();
if (pagesSeen.includes(page + 1)) {
return setPage(page + 1);
}
if (prefetchedOrders) {
const allOrders = orders.concat(prefetchedOrders);
setOrders(allOrders);
setPage(page + 1);
setPagesSeen([...pagesSeen, page + 1]);
setPrefetchedOrders(null);
return;
}
setIsLoading(true);
};
Wait until your component re-render.
const [loading, setLoading] = useState(false);
useEffect(() => {
if (loading) {
doSomething();
}
}, [loading]);
setLoading(true);
You can improve clarity with something like:
function doSomething() {
// your side effects
// return () => { }
}
function useEffectIf(condition, fn) {
useEffect(() => condition && fn(), [condition])
}
function App() {
const [loading, setLoading] = useState(false);
useEffectIf(loading, doSomething)
return (
<>
<div>{loading}</div>
<button onClick={() => setLoading(true)}>Click Me</button>
</>
);
}
Created a custom useState hook which works similar to the normal useState hook except that the state updater function for this custom hook takes a callback that will be executed after the state is updated and component rerendered.
Typescript Solution
import { useEffect, useRef, useState } from 'react';
type OnUpdateCallback<T> = (s: T) => void;
type SetStateUpdaterCallback<T> = (s: T) => T;
type SetStateAction<T> = (newState: T | SetStateUpdaterCallback<T>, callback?: OnUpdateCallback<T>) => void;
export function useCustomState<T>(init: T): [T, SetStateAction<T>];
export function useCustomState<T = undefined>(init?: T): [T | undefined, SetStateAction<T | undefined>];
export function useCustomState<T>(init: T): [T, SetStateAction<T>] {
const [state, setState] = useState<T>(init);
const cbRef = useRef<OnUpdateCallback<T>>();
const setCustomState: SetStateAction<T> = (newState, callback?): void => {
cbRef.current = callback;
setState(newState);
};
useEffect(() => {
if (cbRef.current) {
cbRef.current(state);
}
cbRef.current = undefined;
}, [state]);
return [state, setCustomState];
}
Javascript solution
import { useEffect, useRef, useState } from 'react';
export function useCustomState(init) {
const [state, setState] = useState(init);
const cbRef = useRef();
const setCustomState = (newState, callback) => {
cbRef.current = callback;
setState(newState);
};
useEffect(() => {
if (cbRef.current) {
cbRef.current(state);
}
cbRef.current = undefined;
}, [state]);
return [state, setCustomState];
}
Usage
const [state, setState] = useCustomState(myInitialValue);
...
setState(myNewValueOrStateUpdaterCallback, () => {
// Function called after state update and component rerender
})
you can create a async state hooks
const useAsyncState = initialState => {
const [state, setState] = useState(initialState);
const asyncSetState = value => {
return new Promise(resolve => {
setState(value);
setState((current) => {
resolve(current);
return current;
});
});
};
return [state, asyncSetState];
};
then
const [loading, setLoading] = useAsyncState(false)
const submit = async () => {
await setLoading(true)
dosomething()
}
I have a suggestion for this.
You could possibly use a React Ref to store the state of the state variable. Then update the state variable with the react ref. This will render a page refresh, and then use the React Ref in the async function.
const stateRef = React.useRef().current
const [state,setState] = useState(stateRef);
async function some() {
stateRef = { some: 'value' }
setState(stateRef) // Triggers re-render
await some2();
}
async function some2() {
await someHTTPFunctionCall(stateRef.some)
stateRef = null;
setState(stateRef) // Triggers re-render
}
Pass a function to the setter instead of value!
instead of giving a new value to the setter directly, pass it an arrow function that takes the current state value and returns the new value.
it will force it to chain the state updates and after it's done with all of them, it will rerender the component.
const [counter, setCounter] = useState(0);
const incrementCount = () => {
setCounter( (counter) => { return counter + 1 } )
}
now every time incrementCount is called, it will increase the count by one and it will no longer be stuck at 1.