useEffect does not allow me to update an array inside my component - javascript

I have two issues, the first one was when I wanted to pass an array of objects from a parent component to a child component, something like this:
function DropdownSome ({options, placeholder, someNum}) {
const [value, setValue] = useState (options)
return (
<Dropdown as = {ButtonGroup}>
<Dropdown.Toggle as = {CustomToggle}>
{placeholder}
</Dropdown.Toggle>
<Dropdown.Menu as = {CustomMenu}>
{value.map ((r, index) =>
<Dropdown.ItemText key = {r.id}>
<Counter
index={index}
someNum={someNum}
...
/>
</Dropdown.ItemText>
)}
</Dropdown.Menu>
</Dropdown>
)
}
export default DropdownSome;
Here I couldn't set an array with useState (hooks) because it didn't finish passing my constant value. Then solve this using the useEffect hook, like this:
useEffect (() => {
setValue (options); // this worked to pass options in value
}, [options])
Then I was able to pass my array to my const value, and I was able to use value within my child component. In this component I need to set a value inside my array (setValue (array[someIndex].qty= someNum)). My second issue now is that the useEffect hook is always updating the value of my array to its initial state and this does not allow me to update my array within my child component (array[someIndex].qty // always set at the initial value). I've only tried running useEffect once like this:
useEffect (() => {
setValue (options); // this doesn't work to pass options in value
}, [])
but doing this, my array again fails to pass to my constant value and I feel like I'm stuck here. I've also tried clearing useEffect so that the setValue only runs once, but useEffect keeps setting my variable to its initial state.
useEffect (() => {
const counterInterval = setInterval (() => {
setValue (options);
});
return () => clearInterval (counterInterval);
}, [options]);
I am new to this and some hook life cycle topics are taking me some time to understand, but I hope someone has gone through something similar who can advise me.

You don't need to use useState or useEffect for your component, as far as I can see.
Just do this:
function DropdownSome ({options, placeholder, someNum}) {
return (
<Dropdown as = {ButtonGroup}>
<Dropdown.Toggle as = {CustomToggle}>
{placeholder}
</Dropdown.Toggle>
<Dropdown.Menu as = {CustomMenu}>
{(options || []).map ((r, index) =>
<Dropdown.ItemText key = {r.id}>
<Counter
index={index}
someNum={someNum}
...
/>
</Dropdown.ItemText>
)}
</Dropdown.Menu>
</Dropdown>
)
}
export default DropdownSome;
The || [] is added to handle the case that options are not set when the component mounts (the issue I assume you are trying to handle with your hooks).

This is not really clear, but guess you want to get array of options from parent and then store it as value to modify only value and options in parent to remain unchanged.
In such case you can try to initiate value with empty array. const [value, setValue] = useState ([]);
and then in useEffect update value only if value.length=0
like this:
useEffect (() => {
if (value.legth===0) setValue(options);
}, [options])
If you need to modify options upon change in value than check abt callbacks.

Related

Remove a To do List's item using React

So I'm still learning React and I'm trying to use it to remove an item from a "to do list".
Here is the code:
import { Item } from '../../types/item';
import { useState } from 'react';
type Props = {
item: Item
}
export const ListItem = ({ item }: Props) => {
const [isChecked, setIsChecked] = useState(item.done);
return (
<C.Container done={isChecked}>
<input
type="checkbox"
checked={isChecked}
onChange={e => setIsChecked(e.target.checked)}
/>
<button onClick={removeItem}><img src="./remove"/></button>
<label>{item.name}</label>
</C.Container>
);
}
This button should call the function removeItem that will... remove the item haha.
Any suggestions?
I suggest you to use a functional component with useState hook. Create a state with initial value to be an empty array. then create a function which on click with update the value in state, it canbe done using useState hook. Simply enter setTodo(). This will create a new entry in your state. You can use array methods to add the returned value from function which is triggered onclick, too delete an entry, simply use a filter method, you can look more about it on w3school, it provides good examples which are easy to understand by biggeners.

React TypeError: Cannot use 'in' operator to search for 'length' in null

I want to access a local array file, filter it, and store the filtred array as a state, finally pass the state to child component.
// array file
const originalData =
[
{ "ComName":"A", "SDGs":"1", "No":1 },
{ "ComName":"B", "SDGs":"2", "No":2 },
...
]
I use getComany() and filterCompany() to get the filtred data, and store it in filtredCompany as a state. But it turns out TypeError: Cannot use 'in' operator to search for 'length' in null.
// Services.js
export function getCompany() {
const companyList = originalData;
return companyList;
}
export function filterCompany(comType) {
// filter detail
let companyList = getCompany();
let filtredMatchCompany = getCompany().filter(type => type.SDGs === comType && type.No == comType);
let matchCom = _.map(filtredMatchCompany, 'ComName');
let filtredCompany = companyList.filter((type)=> matchCom.includes(type.ComName))
// Filter.js
export default function Filter() {
const [filtredCompany,setFiltredCompany] = useState(null);
useEffect(() => {
setFiltredCompany(getCompany());
}, []);
function handleD3(e) {
let typeCompany = e.target.value;
typeCompany !== "all"
? setFiltredCompany(filterCompany(typeCompany))
: setFiltredCompany(getCompany());
}
const outputFilter = filtredCompany;
return (
<>
{buttons &&
buttons.map((type, series) => (
<>
<button key={series} value={type.no} onClick={handleD3}>
{type.name}
</button>
</>
))}
<>
<ObjectD3 outputFilter = {filtredCompany}/>
</>
</>
}
The error might come from initial state null. I try to fix that, and change the initial state to
const [filtredCompany,setFiltredCompany] = useState([]) . TypeError doesn't occur, but setState, which is setFiltredCompany , doesn't work anymore.
const [filtredCompany,setFiltredCompany] = useState(null);
console.log(filtredCompany)
// *click button*
// the filtred data I want
const [filtredCompany,setFiltredCompany] = useState([]);
console.log(filtredCompany)
// *click button*
// []
Does anyone know how to handle this situation or better idea to pass data? Thank you so much in advanced!
Source code here
Let go through a part of your code here.
First of all, for a React Component, the lifecycle methods are executed in the following order: constructor -> render -> componentDidMount.
Within the Filter component, you are setting initial state like this:
useEffect(() => {
setFiltredCompany(getCompany());
}, []);
Now, one thing to remember is all the setState() functions and the useEffect hook, are asynchronous, that is, they complete their execution at some time in the future. So, when React renders your app, by the time ObjectD3 component is rendered, the useEffect hook has not executed, so
the ObjectD3 receives null as a prop and the statement in ObjectD3
this.dataset = this.props.outputFilter;
assigns null to the dataset, thereby giving you the error.
A better way to do it, is to implement another lifecycle method in ObjectD3, named componentDidUpdate, where you can compare the changes in props, since the update and take necessary actions.
Check the updated version of code here.

Reactjs closure when passing state to component

I got a react functional component:
const DataGrid = (props) =>
{
const [containerName, setContainerName] = useState("");
const [frameworkComponents, setFrameworkComponents] = useState(
{customLoadingOverlay: LoadingOverlayTemplate,
customNoRowsOverlay: UxDataGridCustomNoRows,
editButton: params => <ViewAndDeleteSetting {...params}
openAddConfigurationsWindow={openAddConfigurationsWindow}
onDeleteSetting={onDeleteSetting}/>,
});
useEffect(async () =>
{
if(props.containerName && props.containerName !== "")
{
setContainerName(props.containerName);
}
},[props.containerName]);
.
.
.
const onDeleteSetting = async (settingKey) =>
{
console.log("ON DELETE AND CONTAINER NAME:");
console.log(containerName); //HERE THE CONTAINER NAME IS EMPTY
...
}
return (
<UxDataGrid
frameworkComponents={frameworkComponents}/>
);
The container name inside useEffect exists and is not empty. As you can see in the comment in onDeleteSetting, the containerName is empty when this callback is invoked. I tried adding this to the useEffect after setContainerName:
setFrameworkComponents({customLoadingOverlay: LoadingOverlayTemplate,
customNoRowsOverlay: UxDataGridCustomNoRows,
editButton: params => <ViewAndDeleteSetting {...params}
openAddConfigurationsWindow={openAddConfigurationsWindow}
onDeleteSetting={onDeleteSetting}/>,
});
That didn't work.
How can I get the name inside the callback? There is no special need to leave that frameworkComponents struct in the state.. it can also be moved to somewhere else if you think its better
Try this in your useEffect, update the onDeleteSetting function with the new containerName when it's updated
.....
useEffect(async() => {
if (props.containerName && props.containerName !== "") {
setContainerName(props.containerName);
// move this function here
const onDeleteSetting = async(settingKey) => {
console.log("ON DELETE AND CONTAINER NAME:");
// use props.containerName since the state update is async
console.log(props.containerName);
...
}
// update your components with the updated functions
setFrameworkComponents(prevComponents => ({
...prevComponents,
editButton: params =>
<ViewAndDeleteSetting
{...params}
openAddConfigurationsWindow={openAddConfigurationsWindow}
onDeleteSetting={onDeleteSetting}
/>,
}));
}
}, [props.containerName]);
.....
This should provide the updated state with the updated function, if it works, I can add more details.
You almost certainly shouldn't be storing it in state. Props are essentially state controlled by the parent. Just use it from props. Copying props to state is usually not best practice.
If you're looking at one of the very rare situations where it makes sense to set derived state based on props, this page in the documentation tells you how to do that with hooks. Basically, you don't use useEffect, you do your state update right away.
Here's a full quote from the linked documentation:
How do I implement getDerivedStateFromProps?
While you probably don’t need it, in rare cases that you do (such as implementing a <Transition> component), you can update the state right during rendering. React will re-run the component with updated state immediately after exiting the first render so it wouldn’t be expensive.
Here, we store the previous value of the row prop in a state variable so that we can compare:
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}`;
}
This might look strange at first, but an update during rendering is exactly what getDerivedStateFromProps has always been like conceptually.
If you did it the same way they did in that example, your component would still render with containerName set to the default state (""), it's just that it will then almost immediately re-render with the updated containerName. That makes sense for their example of a transition, but you could avoid that by making the prop's initial value the state's initial value, like this:
const DataGrid = (props) => {
const [containerName, setContainerName] = useState(props.containerName); // *** ONLY USES THE INITIAL PROP VALUE
const [frameworkComponents, setFrameworkComponents] = useState(
// ...
});
// *** Updates the state value (on the next render) if the prop changes
if (containerName !== props.containerName) {
setContainerName(props.containerName);
}
// ...
};
Every time the containerName prop changes, though, your component will render twice, which brings us back full circle to: Don't store it in state, just use it from props. :-)
Stepping back and looking at the component as a whole, I don't think you need any state information at all, but if your goal is to avoid having the frameworkComponents you pass UxDataGrid change unnecessarily, you probably want useMemo or React.memo rather than state.
For instance, with useMemo (but keep reading):
const DataGrid = ({containerName}) => {
const frameworkComponents = useMemo(() => {
const onDeleteSetting = async (settingKey) => {
console.log("ON DELETE AND CONTAINER NAME:");
console.log(containerName);
// ...
};
return {
customLoadingOverlay: LoadingOverlayTemplate,
editButton: params => <ViewAndDeleteSetting {...params}
openAddConfigurationsWindow={openAddConfigurationsWindow}
onDeleteSetting={onDeleteSetting} />,
};
}, [containerName]);
return (
<UxDataGrid frameworkComponents={frameworkComponents} />
);
};
But if componentName is your only prop, it may well be even simpler with React.memo:
const DataGrid = React.memo(({containerName}) => {
const onDeleteSetting = async (settingKey) => {
console.log("ON DELETE AND CONTAINER NAME:");
console.log(containerName);
// ...
};
return (
<UxDataGrid frameworkComponents={{
customLoadingOverlay: LoadingOverlayTemplate,
editButton: params => <ViewAndDeleteSetting {...params}
openAddConfigurationsWindow={openAddConfigurationsWindow}
onDeleteSetting={onDeleteSetting} />,
}} />
);
});
React.memo memoizes your component, so that your component function is only ever called again when the props change. Since everything in the component needs to update based on the componentName prop changing, that looks like a good match (but I don't know what UxDataGrid is).
The problem was with how I tried passing props to ViewAndDeleteSetting. If you want to pass prop to a cell rendered component, you shouldn't be doing it in frameworkComponents, but rather you need to do it in the column definition like this:
useEffect(() =>
{
let columns = [{headerName: '', cellRenderer: 'editButton', width: 90, editable: false,
cellRendererParams: {
openAddConfigurationsWindow: openAddConfigurationsWindow,
onDeleteSetting: onDeleteSetting
}},
.. other columns
]
setColumnDefinition(columns);
},[props.containerName]);
The columns with the cellRendererParams do gets recreated in the useEffect when the name changes, and then the component can access this params regularly via its props

useState and changes in the props

I'm trying to understand what happens when you have both props and useState in one component.
I wrote little example of it which has one parent component that prints its numbers with another child component -
const MyNumbers = (props) => {
const [numbers, setNumbers] = useState([...props.arr]);
function changeNumbers() {
setNumbers((nums) => [...nums.map(() => Math.floor(Math.random() * 10))]);
}
return (
<div className="MyNumbers">
<div>
<button onClick={changeNumbers}>Chane numbers</button>
</div>
<div>
{numbers.map((num, idx) => (
<SingleNumber key={idx} num={num}></SingleNumber>
))}
</div>
</div>
);
};
const SingleNumber = (props) => {
const [num] = useState(props.num);
useEffect(() => {
console.log("useEffect called");
});
return <h3>The number is {num}</h3>;
};
Here is the above demo
The SingleNumber component uses useState and as you can see clicking on the "Change numbers" action doesn't change the values in the children component.
But when I wrote almost the same code but now SingleNumber doesn't use useState then clicking on the "Change numbers" changes all the values in the children component (like in this demo).
Is it correct to say that a function component with a useState renders once and then only changed if the state changed but not if the props changed ?
OFC the component "rerenders" when the props change, the useEffect hook in SingleNumber is showing you that the "render phase" is run each time the props change.... effects are run each time the component is rendered.
const SingleNumber = (props) => {
const [num] = useState(props.num);
useEffect(() => {
console.log("useEffect called"); // <-- logged each time the component renders
});
return <h3>The number is {num}</h3>;
};
If you added a dependency on props.num and updated the local state (don't actually do this, it's an anti-pattern in react!), you'll see the UI again update each time the props update.
To answer your queston:
Is it correct to say that a function component with a useState renders
once and then only changed if the state changed but not if the props
changed?
No, this is not technically correct to say if "render" to you means strictly react rendered the component to compute a diff, react components rerender when state or props update. Yes, if "render" more generally means you visually see the UI update.
When you call useState it returns an array with two values in it:
The current value of that bit of the state
A function to update the state
If there is no current value when it sets the state to the default value and returns that.
(The default value is the argument you pass to useState).
If you change the values of props in your example, then the component rerenders.
useState returns the current value of that bit of the state. The state has a value, so it doesn't do anything with the argument you pass to useState. It doesn't matter that that value has changed.
Since nothing else has changed in the output, the rerendered component doesn't update the DOM.
Is it correct to say that a function component with a useState renders once and then only changed if the state changed but not if the props changed?
No, it does rerender but doesn't commit the changes.
When parent component MyNumbers re-renders by clicking changeNumbers, by default (unless React.memo used) all its children components (like SingleNumber) will be re-render.
Now when SingleNumber rerenders, notice useState docs.
During the initial render, the returned state (state) is the same as the value passed as the first argument (initialState).
You initial the state useState(props.num) but it can only be changed by calling the setter function, therefore the state num won't change because you never calling the setter.
But it will rerender on parent render as mentioned above (notice the useEffect logs).
You don't need to use useState in SingleNumber.
because useState called only once when it rendered.
const SingleNumber = (props) => {
// const [num] = useState(props.num);
// useEffect(() => {
// console.log("useEffect called");
// });
return <h3>The number is {props.num}</h3>;
};
if you want to use useState, you can use like this.
const SingleNumber = (props) => {
const [num, setNum] = useState(props.num);
useEffect(() => {
console.log("useEffect called");
setNum(props.num);
}, [props.num]);
return <h3>The number is {num}</h3>;
};

When and why to useEffect

This may seem like a weird question, but I do not really see many use cases for useEffect in React (I am currently working on a several thousand-lines React codebase, and never used it once), and I think that there may be something I do not fully grasp.
If you are writing a functional component, what difference does it make to put your "effect" code in a useEffect hook vs. simply executing it in the body of the functional component (which is also executed on every render) ?
A typical use case would be fetching data when mounting a component : I see two approaches to this, one with useEffect and one without :
// without useEffect
const MyComponent = () => {
[data, setData] = useState();
if (!data) fetchDataFromAPI().then(res => setData(res));
return(
{data ? <div>{data}</div> : <div>Loading...</div>}
)
}
// with useEffect
const MyComponent = () => {
[data, setData] = useState();
useEffect(() => {
fetchDataFromAPI().then(res => setData(res))
}, []);
return(
{data ? <div>{data}</div> : <div>Loading...</div>}
)
}
Is there an advantage (performance-wise or other) to useEffect in such usecases ?
I. Cleanup
What if your component gets destroyed before the fetch is completed? You get an error.
useEffect gives you an easy way to cleanup in handler's return value.
II. Reactions to prop change.
What if you have a userId passed in a props that you use to fetch data. Without useEffect you'll have to duplicate userId in the state to be able to tell if it changed so that you can fetch the new data.
The thing is, useEffect is not executed on every render.
To see this more clearly, let's suppose that your component MyComponent is being rendered by a parent component (let's call it ParentComponent) and it receives a prop from that parent component that can change from a user action.
ParentComponent
const ParentComponent = () => {
const [ counter, setCounter ] = useState(0);
const onButtonClicked = () => setCounter(counter + 1);
return (
<>
<button onClick={onButtonClicked}>Click me!</button>
<MyComponent counter={counter} />
</>
);
}
And your MyComponent (slightly modified to read and use counter prop):
const MyComponent = ({ counter }) => {
[data, setData] = useState();
useEffect(() => {
fetchDataFromAPI().then(res => setData(res))
}, []);
return(
<div>
<div>{counter}</div>
{data ? <div>{data}</div> : <div>Loading...</div>}
</div>
)
}
Now, when the component MyComponent is mounted for the first time, the fetch operation will be performed. If later the user clicks on the button and the counter is increased, the useEffect will not be executed (but the MyComponent function will be called in order to update due to counter having changed)!
If you don't use useEffect, when the user clicks on the button, the fetch operation will be executed again, since the counter prop has changed and the render method of MyComponent is executed.
useEffect is handling the side effect of the problem. useEffect is the combination of componentDidMount and componentDidUpdate. every initial render and whenever props updated it will be executed.
For an exmaple:
useEffect(() => {
fetchDataFromAPI().then(res => setData(res))
}, []);
Another example:
let's assume you have multiple state variables, the component will re-render for every state values change. But We may need to run useEffect in a specific scenario, rather than executing it for each state change.
function SimpleUseEffect() {
let [userCount, setUserCount] = useState(0);
let [simpleCount, setSimpleCount] = useState(0);
useEffect(() => {
alert("Component User Count Updated...");
}, [userCount]);
useEffect(() => {
alert("Component Simple Count Updated");
}, [simpleCount]);
return (
<div>
<b>User Count: {userCount}</b>
<b>Simple Count: {simpleCount}</b>
<input type="button" onClick={() => setUserCount(userCount + 1}} value="Add Employee" />
<input type="button" onClick={() => setSimpleCount(simpleCount + 1}} value="Update Simple Count" />
</div>
)
}
In the above code whenever your props request changed, fetchDataFromAPI executes and updated the response data. If you don't use useEffect, You need to automatically handle all type of side effects.
Making asynchronous API calls for data
Setting a subscription to an observable
Manually updating the DOM element
Updating global variables from inside a function
for more details see this blog https://medium.com/better-programming/https-medium-com-mayank-gupta-6-88-react-useeffect-hooks-in-action-2da971cfe83f

Categories