Call Hooks on component change in React Native - reactjs

I'm working on an react native app.
This app use a database, the main component use 2 differents hook.
The first hook retrieves the results of a SQL query and store them in a variable.
The second hook creates a list from the first variable
Like this:
const [people, setPeople ] = useState([]);
useEffect (() => {
db.getAllPeople().then(row => setPeople(row))
},[])
const [listData, setListData] = useState([]);
useEffect(()=> {
setListData(
Array(people.length)
.fill('')
.map((_, i) => ({ key: `${i}`, name: `${people[i].name}`}))
)
}, [people]);
After that, my main component displays a SwipeList from the results.
Here is the problem. I am using another component to add an element to my database. When I return to my main component I would like this new element to be displayed. But the problem is that the 2 hooks are not called on the component change and the list therefore remains unchanged.
I've tried to use the useFocusEffect but it doesn't work in my case.
Any suggestions ?

I think the useState hook manages the state of the component itself, unless you are passing this state among your parent and child or using callbacks to set the state on the component that you want to render, you could use a single source of truth to handle the changes in data, react itself will notice this changes and therefore, render the changed screens, considering that you have asynchronous operations when querying the database, a combination of redux and redux saga may help you.
https://github.com/redux-saga/redux-saga

There're one issues with your current code, or potential issues
Your second useEffect might get called when people becomes an empty list, this will reset your list data. The cure is to put a if statement inside, ex.
useEffect(()=> {
if (!people) return;
setListData(...)
}, [people]);
To be honest, if these two lists are connected, you shouldn't use two hook. The best way is to define listData
const listData = (a function that takes people as input), ex.
const listData = people.map(v => v)
Of course, there might be a reason why you'd like to introduce more hook in complex situation, ex. useRef, useMemo.

Related

How to force refresh of GrandParent component from grandchild component with React hooks in SPFx

In the main component of a SPFx web part, I have a data load running in a React.useEffect
const [jobsGets, setJobsGets] = React.useState(0);
React.useEffect(() => {
{Some code to get data from SharePoint}
});
}, [jobsGets]);
So a refresh will happen when the value of "jobsGets" will change, right?
This component calls another component that calls another and the last has an event that should refresh by changing "jobsGets".
If I write a fanction and pass it down the props of each generation's component,
It ends running in the main component forever. So this is not a good solution.
How do I setJobsGets(jobsGets+1); from the grandchild components?
Any hint is welcome.
Thank you
you are likely updating jobs at grandChild with useEffect, that runs on mount and every update. if you pass a second empty array [] param, this update will run only after first render if that's your specific case, avoiding retriggers:
// at some granchild
useEffect(() => { ...logic here }, [])
if useEffect should run after on specific param's update, you pass that param as argument to the array. useEffect will run only on changes for the params dependencies set on [].
useEffect(() => { ...logic here }, [param])
you may also look at useContext for passing down state better at deep tree.

When updating state from props, why useEffect() instead of checking directly in the component?

When updating state when props change, the commonly held approach is to use useEffect() with the prop as a dependency:
const Component = ({prop}) => {
const [state, setState] = useState(prop);
useEffect(() => {
setState(prop);
}, [prop]);
return <div>{state}</div>;
}
I have to wonder, is there an advantage to this over doing a comparison directly in the component itself, like so:
const Component = ({prop}) => {
const [state, setState] = useState(prop);
if (prop !== state) {
setState(prop);
}
return <div>{state}</div>;
}
It looks like both approaches cause the component to execute twice -- once with the prop and state out of sync, once in sync -- but the second approach looks like it avoids adding a hook to the stack. It also looks like it could be optimized out of the DOM reconciliation, since it doesn't have to wait for the DOM to be generated the way useEffect() does. I'm not even sure it is easier to read, other than being "The Hooks Way."
Does anyone have an idea why the useEffect() route could be better than the inline check?
The official React docs use the second approach for syncing props to state:
https://reactjs.org/docs/hooks-faq.html#how-do-i-implement-getderivedstatefromprops
function ScrollView({row}) {
const [isScrollingDown, setIsScrollingDown] = useState(false);
const [prevRow, setPrevRow] = useState(null);
if (row !== prevRow) {
// Row changed since last render. Update isScrollingDown.
setIsScrollingDown(prevRow !== null && row > prevRow);
setPrevRow(row);
}
return `Scrolling down: ${isScrollingDown}`;
}
The difference between updating state in useEffect and updating state during render is that, useEffect is called after React already commits the updates, i.e. the updates would be reflected in DOM, then you'll update state which will update the DOM again. The second way causes a re-render, but there's only one commit to the DOM.
I think it's important to understand when to use hooks and when not too.
The answer to this question is a very helpful How does React implement hooks so that they rely on call order.
We have recently discovered a lot of problems to do with the useEffect hook, one of them being
useEffect(() => {
// Called on first render and every props.foo update
}, [props.foo])
We didn't want it to be called on the first render only on every update.
Another on being useEffect gives warning when using objects & arrays, instead of a value.
I'm not saying don't use the useEffect ever I love the useEffect hook, but instead of saying why shouldn't use it. You should say do I need useEffect to get what I need to achieve.
For your example unless you are setting the state at another point I would suggest. Just using the prop. If you are setting the state I would have a look at the above link at the schema found.
The schema of a single hook is as below. It can be found in the implementation
function createHook(): Hook {
return {
memoizedState: null,
baseState: null,
queue: null,
baseUpdate: null,
next: null,
};
}
Think do I need to make use of the hooks queue when setting the state, if not then don't use a hook.
Another good use of the hook is are you going to be putting multiple values in the hook array if so it's a good idea to use it then!
Hope that helps :)

Is there any way to see names of 'fields' in React multiple state with React DevTools when using 'useState' hooks

I've been learning/experimenting with React hooks. When I go to inspect the values of the current state of a component using React DevTools in Chrome, I see the state fine, but the actual 'fields' -- that is, the state variables that are being updated by the individual useState hooks -- don't have any name associated with them. Instead, I see, for example, several strings, a couple of booleans, etc. I can generally figure out what's going on, but this seems problematic -- I'd like to be able to see which what the state variable's name is.
For instance, if I have something like
const [doughnuts, setDoughnuts] = useState(24)
When I look in React DevTools I'd like to see something like `doughnuts: number : 24', instead of just 'number: 24'.
Am I missing some setting somewhere, or some technique to turn on this ability?
Finally react team listened to us
The recent introduction of parsing custom hooks in react dev tools option might help
.
Before parsing ( before clicking the magic button in custom hooks card )
.
.
After parsing ( clicking the magic button in the top right )
Some approaches not mentioned in the other answers:
Use the following:(suggested by Oleh)
const [{ item }, setItem] = useState({ item: 2 });
You could also wrap the useState function so that, based on the shape of the initial value, the setItem function it returns auto-converts from the passed value itself into an object with the correct (wrapper) shape.
Create a new useStateWithLabel function:
function useStateWithLabel(initialValue, name) {
const [value, setValue] = useState(initialValue);
useDebugValue(`${name}: ${JSON.stringify(value)}`);
return [value, setValue];
}
It's based on the useDebugValue function described here.
Usage:
const [item, setItem] = useStateWithLabel(2, "item");
You are not missing anything and you can't change this behaviour. This is how React deals with multiple state.
https://reactjs.org/docs/hooks-rules.html#explanation.
One way to avoid this problem is to use a single State Hook which creates a single state including all the data.
const [state, setState] = useState({doughnuts: 24, key1: 'value1', key2: 'value2'});
In this case the state is stored in a single object and each value is associated with a key.
Take a look at this: Should I use one or many state variables?
A compound state is hard to manage, but there is a tool which can help you with that: useReducer Hook
When you do the following operation
const [item, setItem] = useSate(2)
You're using destructuring assignment in an array a type which does not contain a key like an object. You're just creating an alias to access the first element of the array returned by useState. If you do something like this
const [item, setItem] = useState({value: 2})
You will be able to see value: 2 in your dev-tools, cause it reflects the current state of that hook at a certain point of time.
Each time you call a Hook, it gets isolated local state within the currently executing component based on the previous value, so the identifier attributed by you (item) will only be scoped to that render cycle, but it doesn't mean that React reference is using the same identifier.
You can use useDebugState hook from use-named-state package.
import { useDebugState } from "use-named-state";
const App = () => {
const [counter, setCounter] = useDebugState("counter", 0);
return <button onClick={(prevCount) => prevCount + 1}>{counter}</button>;
};
It internally uses useDebugValue hook from react (method suggested by #Venryx)

React Hooks - where to put the logic that process passed props only once?

Sometimes a component needs to process passed props and save in its state. Since the processing can be heavy it's good to be done just once. Before hooks, it's typically done in constructor or componentDidMount.
Now coming into hooks, it can be achieved with useEffect, passing [] as the second parameter to run only once but I feel it's not the best place - what we're doing is processing props and save in state, which is not a Side Effect. From the docs: "Data fetching, setting up a subscription, and manually changing the DOM in React components are all examples of side effects." Don't think pre-process belongs any of these.
So where's the best place to do it with hooks?
import React, {useMemo} from 'react';
const someExpensiveFunction = (a, b) => {
// expensive stuff happens here
}
const someFunctionalComponent = ({ prop1, prop2 }) => {
const someVariableDependentOnProps = useMemo(() => someExpensiveFunction(prop1, prop2), [prop1, prop2]);
return <div>{someVariableDependentOnProps}</div>
}
According to the docs:
useMemo will only recompute the memoized value when one of the dependencies has changed. This optimization helps to avoid expensive calculations on every render.
https://reactjs.org/docs/hooks-reference.html#usememo

React JS Freezes Browser

I have a React component which has 2000 elements and based on some filter conditions I update my state, which internally causes re-rendering. Everything seems to be working fine. But when I togglefilter from 2000 elements to say 1000 elements and back&forth, the rendering takes a lot of time and sometimes the browser freezes. I did chrome timeline profiling, the major time consuming piece is rendering. Any help would be appreciated.
As suggested by #enjoylife is a great step but what if you have many components structures in your view, that would be very difficult to debug even memoising the component won't be able to subside the continuous or loop rendering.
I learnt this after I ran into strange freezing and weird error that wouldn't stop any time a user logged in on the homepage. Imagine of all screens. Sometimes, you would hardly notice your component re-rending.
Detect your screen/page (loop) re-rendering with console log
const Home = () => {
conso.log('home re-rending')
// some hooks
return <BigComponent />
}
As written above. The logs must not show more than a limited time deemed after a component has mounted. In my case, it's once. But if it is too much(logs) and would certainly freeze your pc. Therefore, follow the below steps carefully and retrace your steps.
Tips and prerequisite before trying out this proposed solution. Please make sure you have style guide setup e.g. Eslint, it's great. In my case, I reproduced the source code with cra, then sorted out the first and last listed problem which I encountered.
Be careful with the use of React hooks such as useEffect especially. Avoid causing a side effect in a component.
In my case, I created a reusable useUpdateEffect hook and what I intend it to solve as par the name was to detect an update of React props or window props, but it backfires, I won't share the code.
Also, do extra check if you passed correct and expected dependencies, on this Eslint deserve an accolade.
Avoid random keys in React list. Use unique and constant keys in a component list as react depend on it to identify each item. According to react library
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. You may use the item index as a key as a last resort:
Avoid variable name conflict in your reducer and React component. Please consider the use of style guides as your friend to avoid this fall.
I made the stupid mistake to create a Foo class and use in its render function, which also leads to the freezing scene. Write here for anyone who could meet this problem again.follow this thread.
Avoid infinite loops, Imagine rendering a lot of data at a go. this happen
just in case you share my fate, I urge you to check your loops and make sure you do not have a += instead of -= (or vice versa). Those infinite loops can be quite a big pain in the neck.
Keep your reducer as a reducer, Avoid Action creator, an API call in your reducer or using another reducer in your reducer so, for instance, reducerA in reducerB. When you call to update reducerA in reducerB, the update in reducerA would trigger an update in reducerB whereby cause page/screen to re-render multiple times. for example
// this react reducer in my case
// reducer js file - reducerB
const useBusinesses = () => {
// reducerB as discussed above - the loading context
const { loading } = useLoadingContext(); // the culprit
const [data, setData] = useState(initialState); // initial state,
const [state, dispatch] = useReducer(reducer, data);
useEffect(() => setData(state), [state, setData]);
const { businesses, errorMessage } = state;
const setBusinesses = (payload) => dispatch({ type: `${FETCH_BUSINESSES}_SUCCESS`, data: payload });
const setBusinessesError = (payload) => dispatch({ type: `${FETCH_BUSINESSES}_ERROR`, data: payload });
const fetchBusinesses = async (lglt, type = 'food', limit = 12) => {
try {
// update reducerB: triggers multiple update in reducerA while requesting is pending
loading(FETCH_BUSINESSES, true);
const request = await API.businesses.getWithquery(
`long=${lglt[0]}&latt=${lglt[1]}&limit=${limit}&type=${type}`
);
loading(FETCH_BUSINESSES, false);
setBusinesses(request.data);
} catch (err) {
loading(FETCH_BUSINESSES, false);
// if (!err.response) dispatch(alertMessage(FETCH_BUKKAS, true, 'Please check your network'));
setBusinessesError(err.response.data);
}
});
return { businesses, errorMessage, fetchBusinesses };
};
export const [BusinessesProvider, useBusinessesContext] = constate(useBusinesses);
//home js file
Home = () => {
const { fetchBusinesses } = useBusinessContext();
conso.log('home re-rending')
// some hooks
useEffect(() => {
console.log('am i in trouble, yes!, how many troubles')
fetchBusinesses(coordinates)
}, [fetchBusinesses, coordinates])
return <BigComponent />
}
A quick fix is to implement shouldComponentUpdate See the docs, for whichever child component is being rendered ~2000 times.
shouldComponentUpdate: function(nextProps, nextState) {
return this.props.value !== nextProps.value;
}
Another quick check is to ask yourself if your following the convention of using small, stateless children, passing only props. If not, it might be time to refactor.

Resources