React Child Component does not update properties passed from Parent Component - javascript

I have a parent component and a child component as function components in react.
Parent Component
const Parent = () => {
const selectedProduct = 0;
//const [servicesList, setServicesList] = useState<IService[]>([]);
var servicesList = [] as IService[];
var operationalProcessSelector = [] as IOperationalProcessSelector[];
useEffect(()=>{
fetchServicesList(840);
}, []);
const fetchServicesList = async (quotationId: number) => {
const SERVICE_LIST_API_URL = `API URL...`;
await axios.get(SERVICE_LIST_API_URL, {headers:API_REQ_HEADERS})
.then((response:any)=>{
if(response && response.data && response.data.length > 0 && response.data[0]){
let product = response.data[selectedProduct];
let operationalProcesses = product.OperationalProcess;
updateServiceList(operationalProcesses);
}
});
};
const updateServiceList = (operationalProcesses: any) => {
operationalProcesses.forEach((operationalProcessItem: any)=>{
operationalProcessItem.Services.forEach((serviceItem: any) => {
serviceItem.LineEditState = 'edit';
serviceItem.ShowContent = true;
});
});
const flattenedArray: IService[] = operationalProcesses.reduce(
(accumulator: IService[], value: any) => accumulator.concat(value.Services),
[] as IService[]);
//servicesList(flattenedArray);
servicesList = flattenedArray;
console.log('Update Services', servicesList); // It retrieves and outputs data here
};
return <ServiceGrid OperationalProcessSelector={operationalProcessSelector} />
}
export default Parent
Child Component
const ServiceGrid = (props: Prop) => {
console.log("Component ServiceGrid generated");
const [tableData, setTableData] = useState<IService[]>(props.ServicesList);
useEffect(()=>{
console.log('component', tableData); // It does not return the value
});
return (
<div>{tableData.map((e: any)=>e.childData)}</div> // It does not render the value here
);
};
export default ServiceGrid;
interface Prop {
OperationalProcessSelector: IOperationalProcessSelector[],
ServicesList: IService[]
}
Question: why tableData in child component does not load?
I am not sure whether I should use State or just a plain variable here. But once I receive the value to the child, I might update the child variable and it should automatically reflect in the child component.
PS:
I have tried useState() in both parent and child components as well.
Parent Component variables
const [servicesList, setServicesList] = useState<IService[]>([]);
const [operationalProcessSelector, setOperationalProcessSelector] = useState<IOperationalProcessSelector[]>([]);
even after using setTableData function it does not reflect.
Child Component variables
useEffect(()=>{
setTableData(props.ServicesList);
// setOperationalProcessSelector(props.OperationalProcessSelector);
console.log('component', props.ServicesList);
}, []);

you should pass props.ServicesList to useEffect dependency array.
useEffect(()=>{
setTableData(props.ServicesList);
// setOperationalProcessSelector(props.OperationalProcessSelector);
console.log('component', props.ServicesList);
}, [props.ServicesList]);

Related

React function uses old state used when creating instance of subcomponent

I have a ToDo component and an Item component in my react app. I noticed that when I trigger my onDelete function from my Item component, it only has the tdList state variable in the state it was in when I created the item component. Why does this happen and how can I fix this issue.
function ToDo() {
const [tdList, setTD] = useState([]);
const [item, setItem] = useState("");
const onDelete = (id) => {
// console.log(id);
console.log(tdList);
for(let i=0; i<tdList.length; i++){
if (tdList[i].props.id == id){
// setTD(tdList.splice(i, 1))
}
}
// setTD(tdList.splice())
};
const onHandleSubmit = (event) => {
event.preventDefault();
setTD([...tdList, (<Item id={itemsAdded} item={item} delete={onDelete} />)]);
setItem('');
// console.log(tdList);
itemsAdded++;
};
...more code...
Don't put React components into state. It breaks the natural order of how they're supposed to work and can make the control flow that's been written very difficult to understand. Instead, into state, put only the values needed to create React components from later - and when returning from the component, create the components from that state.
For your code, you could do something like:
const [lastIdUsed, setLastIdUsed] = useState(-1); // use this instead of reassigning a non-React itemsAdded variable
const [tdData, setTdData] = useState([]);
const onDelete = (id) => {
// use `.filter`, not `.splice` in React - don't mutate state
setTdData(tdData.filter(tdItem => tdItem.id !== id));
};
const onHandleSubmit = (event) => {
event.preventDefault();
setTdData([...tdData, { id: lastIdUsed + 1, item }]);
setItem('');
setLastIdUsed(lastIdUsed + 1);
};
const tds = tdData.map(
tdItem => <Item id={tdItem.id} item={tdItem.item} delete={onDelete} />
);
And then with the tds, return them or interpolate them into the JSX at the end.
Only create components right before you're going to return them.

Too many re-renders - while trying to put props(if exists) in state

I am transfer an props from father component to child component.
On the child component I want to check if the father component is deliver the props,
If he does, i"m putting it on the state, If not I ignore it.
if(Object.keys(instituteObject).length > 0)
{
setInnerInstitute(instituteObject)
}
For some reason the setInnerInstitute() take me to infinite loop.
I don't know why is that happening and how to fix it.
getInstitutesById() - Is the api call to fetch the objects.
Father component(EditInstitute):
const EditInstitute = props => {
const {id} = props.match.params;
const [institute, setInstitute] = useState({})
useEffect(() => { //act like componentDidMount
getInstitutesById({id}).then((response) => {
setInstitute(response)
})
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [])
return (
<React.Fragment>
<InstituteForm instituteObject={institute.object}/>
</React.Fragment>
)
}
Child component(InstituteForm):
const InstituteForm = (props) => {
const {instituteObject = {}} = props // if not exist default value = {}
const [innerInstitute, setInnerInstitute] = useState({})
if (Object.keys(instituteObject).length > 0) // if exists update the state.
{
setInnerInstitute(instituteObject)
}
return (
<React.Fragment>
not yet.
</React.Fragment>
)
}
Thanks
I think the way you are changing your InstituteForm's state causing this error. You can try using the useEffect hook to change your innerInstitute based on instituteObject. That's why you need to also add instituteObject in the dependency array of that useEffect hook.
import { useEffect, useState } from "react"
const InstituteForm = (props) => {
const {instituteObject = {}} = props // if not exist default value = {}
const [innerInstitute, setInnerInstitute] = useState({})
useEffect(() => {
// this is be evoked only when instituteObject changes
if (Object.keys(instituteObject).length > 0){
setInnerInstitute(instituteObject)
}
}, [instituteObject])
return (
<React.Fragment>
not yet.
</React.Fragment>
)
}

How to prevent a state update on a react onClick function (outside useEffect)?

When I use useEffect I can prevent the state update of an unmounted component by nullifying a variable like this
useEffect(() => {
const alive = {state: true}
//...
if (!alive.state) return
//...
return () => (alive.state = false)
}
But how to do this when I'm on a function called in a button click (and outside useEffect)?
For example, this code doesn't work
export const MyComp = () => {
const alive = { state: true}
useEffect(() => {
return () => (alive.state = false)
}
const onClickThat = async () => {
const response = await letsbehere5seconds()
if (!alive.state) return
setSomeState('hey')
// warning, because alive.state is true here,
// ... not the same variable that the useEffect one
}
}
or this one
export const MyComp = () => {
const alive = {}
useEffect(() => {
alive.state = true
return () => (alive.state = false)
}
const onClickThat = async () => {
const response = await letsbehere5seconds()
if (!alive.state) return // alive.state is undefined so it returns
setSomeState('hey')
}
}
When a component re-renders, it will garbage collect the variables of the current context, unless they are state-full. If you want to persist a value across renders, but don't want to trigger a re-renders when you update it, use the useRef hook.
https://reactjs.org/docs/hooks-reference.html#useref
export const MyComp = () => {
const alive = useRef(false)
useEffect(() => {
alive.current = true
return () => (alive.current = false)
}
const onClickThat = async () => {
const response = await letsbehere5seconds()
if (!alive.current) return
setSomeState('hey')
}
}

React does not re-render when state changes

I have a list of warehouses that I pull from an API call. I then render a list of components that render checkboxes for each warehouse. I keep the state of the checkbox in an object (using the useState hook). when I check/uncheck the checkbox, I update the object accordingly.
My task is to display a message above the checkbox when it is unchecked. I tried simply using the object, however, the component was not re-rendering when the object changed.
I found a solution to my problem by simply adding another useState hook (boolean value) that serves as a toggle. Since adding it, the component re-renders and my object's value is read and acted on appropriately.
My question is: why did I have to add the toggle to get React to re-render the component? Am I not updating my object in a manner that allows React to see the change in state? Can someone explain to me what is going on here?
I've created a sandbox to demonstrate the issue: https://codesandbox.io/s/intelligent-bhabha-lk61n
function App() {
const warehouses = [
{
warehouseId: "CHI"
},
{
warehouseId: "DAL"
},
{
warehouseId: "MIA"
}
];
const [warehouseStatus, setWarehouseStatus] = useState({});
const [toggle, setToggle] = useState(true);
useEffect(() => {
if (warehouses.length > 0) {
const warehouseStates = warehouses.reduce((acc, item) => {
acc[item.warehouseId] = true;
return acc;
}, {});
setWarehouseStatus(warehouseStates);
}
}, [warehouses.length]);
const handleChange = obj => {
const newState = warehouseStatus;
const { name, value } = obj;
newState[name] = value;
setWarehouseStatus(newState);
setToggle(!toggle);
};
return warehouses.map((wh, idx) => {
return (
<div key={idx}>
{!warehouseStatus[wh.warehouseId] && <span>This is whack</span>}
<MyCheckbox
initialState
id={wh.warehouseId}
onCheckChanged={handleChange}
label={wh.warehouseId}
/>
</div>
);
});
}
Thanks in advance.
You are mutating state (don't mutate state)
this:
const handleChange = obj => {
const newState = warehouseStatus;
const { name, value } = obj;
newState[name] = value;
setWarehouseStatus(newState);
};
should be:
const handleChange = ({name,value}) => {
setWarehouseStatus({...warehouseStatus,[name]:value});
};
See the problem?
const newState = warehouseStatus; <- this isn't "newState", it's a reference to the existing state
const { name, value } = obj;
newState[name] = value; <- and now you've gone and mutated the existing state
You then call setState with the same state reference (directly mutated). React says, "hey, that's the same reference to the state I previously had, I don't need to do anything".

Jest/Enzyme | Redux prop is not defined in test

I am using React-Redux, in a connected component and I want to test if a particular component is rendered. In order for that component to render 2 things must be true:
ListUsers must be an empty array
The securityMode should be basic.
I have already defined the securityMode in my component Props, with no problem. But the ListUsers prop, is coming through redux.
function mapStateToProps(state) {
return {
securityMode: securityModeSelector(state),
usersList: state.users.list,
usersListFetching: state.users.listFetching
};
}
This is my component logic that should be tested:
renderNoResourceComponent = () => {
const { usersList, securityMode } = this.props;
const { selectedGroups } = this.state;
const filteredData = filterUserData(usersList, selectedGroups);
if (filteredData && filteredData.length === 0 && securityMode === 'BASIC') {
return (
<div className="center-block" data-test="no-resource-component">
<NoResource>
.............
</NoResource>
</div>
);
}
return null;
};
And this is the test I wrote:
describe('BASIC securityMode without Data', () => {
const props = {
securityMode: 'BASIC',
listUsers: () => {},
usersList: [] // This is the redux prop
};
it('should render NoResource component', () => {
const wrapper = shallow(<UsersOverviewScreen {...props} />);
const renderUsers = wrapper.find(`[data-test="no-resource-component"]`);
expect(renderUsers).toHaveLength(1);
});
});
But I get an error saying the userLists is not defined. How do I pass this redux prop so my component would pass. `I also need that prop for another set of tests, that needs data, which I need to mock.
Can someone guide me through this? Thank you..
What you want to do is export the component before its connocted to Redux and pass all the props it needs manually:
export class UsersOverviewScreen extends Component {
// ... your functions
render() {
return (
// ... your componont
);
}
}
function mapStateToProps(state) {
return {
securityMode: securityModeSelector(state),
usersList: state.users.list,
usersListFetching: state.users.listFetching
};
}
export default connect(mapStateToProps)(UsersOverviewScreen);
Now, in your tests you can import { UsersOverviewScreen } form 'path/to/UsersOverviewScreen';. You can create the props and pass it to the component like this:
const mockUsersLists = jest.fn(() => usersList || []);
const wrapper = shallow(<UsersOverviewScreen {...props} usersList={mockUsersLists} />);

Categories

Resources