How to simulate parent component state while testing with jest - javascript

I have this component, props are passed from parent component. Ingredients and activeIngredients are stored in the state of parent component.
export const IngredientsBox = ({
ingredients = [],
activeIngredients = [],
onAddIngredientHandler,
onRemoveIngredientHandler,
onResetIngredientsHandler
}) => {
return (
<Div>
{ingredients.map((name) => {
return (
<IngredientButton
name={name}
key={name}
isActive={activeIngredients.includes(name)}
onAddIngredientHandler={onAddIngredientHandler}
onRemoveIngredientHandler={onRemoveIngredientHandler}
/>
);
})}
<ResetButton onResetIngredientsHandler={onResetIngredientsHandler}></ResetButton>
</Div>
);
};
export const IngredientButton = ({ name, isActive, onAddIngredientHandler, onRemoveIngredientHandler }) => {
const onClick = isActive ? onRemoveIngredientHandler : onAddIngredientHandler;
return (
<Button active={isActive} onClick={() => onClick(name)}>
{name}
</Button>
);
};
What I wanna do is to test this component in isolation, but can't figure out how to imitate parent component state to change dynamically, after every method call.
import React from 'react';
import {IngredientsBox} from './IngredientsBox';
import renderer from 'react-test-renderer';
// ingredients and activeIngredients are supposed to imitate parent component state
let ingredients = ['sugar', 'honey', 'mustard', 'watermelon'];
let activeIngredients = [];
const onAddIngredientHandler = (name) => {
activeIngredients = [...activeIngredients, name]
}
const onRemoveIngredientHandler = (name) => {
activeIngredients = activeIngredients.filter((value) => value !== name)
};
test('Button toggle the class on click', () => {
const component = renderer.create(
<IngredientsBox
ingredients={ingredients}
activeIngredients = {activeIngredients}
isActive = {activeIngredients.includes(name)}
onAddIngredientHandler = {onAddIngredientHandler}
onRemoveIngredientHandler = {onRemoveIngredientHandler}
/>
);
let tree = component.toJSON();
expect(tree).toMatchSnapshot();
tree.children[0].props.onClick(); // I expect this to add this element to activeIngredients and it ofcourse works.
tree = component.toJSON();
expect(tree).toMatchSnapshot();
tree.children[0].props.onClick(); // I expect this to remove this element from activeRecipes, it doesn't work, it adds it one more time, and so on. I understand this behaviour is because onClick method was assigned at the beginning and it doesn't change.
tree = component.toJSON();
expect(tree).toMatchSnapshot()
})
Is there any way to make it behave like react component with render() method? So it rerender with fresh state each time?

Related

React array hooks don't work, new values don't display

import React, { useState } from 'react';
import Tab from 'react-bootstrap/Tab';
import Tabs from 'react-bootstrap/Tabs';
import { sections } from '../../data/sections';
export const NavigationTop = () => {
const [mySections, setMySections] = useState(sections);
const selectSection = (id) => {
let newSections = mySections;
newSections[id].name = mySections[id].name + '*';
setMySections(newSections);
};
return (
<Tabs defaultActiveKey="0" id="fill-tab-example" className="mb-3" onSelect={(k) => selectSection(k)} fill>
{mySections.map((el) => {
const { id, name } = el;
return (
<Tab id={id} key={id} eventKey={id} title={name}></Tab>
);
})}
</Tabs>
);
}
The selectSection event is triggered and newSections contains the new values, but the page does not show the new values.
Where is my error?
You are mutating the state object and not providing a new array reference for React's reconciliation process to trigger a component rerender.
const [mySections, setMySections] = useState(sections);
const selectSection = (id) => {
let newSections = mySections; // <-- reference to state
newSections[id].name = mySections[id].name + '*'; // <-- mutations
setMySections(newSections); // <-- same reference
};
The mySections state reference never changes so React bails on rerendering the component. Shallow copy all state, and nested state, that is being updated.
Use a functional state update to correctly update from any previous state.
Example:
const selectSection = (id) => {
setMySections(sections => sections.map(section =>
section.id === id
? { ...section, name: section.name + "*" }
: section
));
};
try this change
let newSections = [...mySections];
what this does is make a copy of your array.
if you don't make a copy, reference doesn't change, and react does a shallow comparison ( only checks the reference and not value ) to see if UI needs to be updated.

Passing data from a child component to a parent component (React)

I have a child component that has some states which change in value when you check or un-check some boxes; I want to be able to pass those state values to the parent component. I've tried a few things on the internet but nothing seem to work.
I've tried to (Parent Component File):
const ParentComponent = () => {
const [ data, setData ] = useState();
const childToParent = (childData) => {
setData(childData);
};
return(
<Child childToParent={childToParent} />
);
}
and on the Child Component:
const ChildComponent = ({ childToParent }) => {
const [ childData, setChildData ] = useState(false);
const handleChange = () => {
setChildData(!childData);
childToParent(childData);
};
return(
<div>
<Checkbox value={childData} callback{() => handleChange()} />
</div>
);
}
Basically the problem is I dont get how to move data from a child component to a parent component.

Props and onChildClick not working together

I have a parent component "Item" and a child component "Order".
Item has a button that toggles whether "Order" is displayed. If book is displayed, it passes the fetched details as props to the Order component, as well as the function for toggling if its open or closed.
Before adding the props to "Order", the toggle worked perfectly. After adding the props, the prop-handling works as it should, but now the function doesn't work. What am i doing wrong?
const Item = () => {
const [item, setItem] = useState('');
const [order, setOrder] = useState('');
//Api call to get item
const orderOpenClose = () => {
setOrder(!order);
};
return (
<>
<div onClick={orderOpenClose}>
<Button text="Order"></Button>
</div>
{order ? <Order acc={item} onChildClick={orderOpenClose}/> : ""}
</>
)
}
const Order = (props, { onChildClick }) => {
const { item } = props;
return (
<>
<div onClick={onChildClick}>
x
</div>
<p>{item.title}</p>
)
}```
This (props, { onChildClick }) is just not correct syntaxis, you can either destruct props or pass them as one object, but not both, so you can do either
const Book = ({acc, onChildClick })
or
const Book = (props) => {
const { acc,onChildClick } = props;

Pass functional component from child to parent in React

Is it possible to pass a functional component from a child component to a parent component? I'm trying to do a dynamic modal that is displayed inside the parent but that the children can populate through a function from a provider, for example:
setModal(() => (
<div>content</div>)
)
And the parent receives this component:
const [modal, setModal] = useState(false)
const [modalContent, setModalContent] = useState<FunctionComponent>()
...
<Provider value={{
setModal: (content: FunctionComponent) => {
setModalContent(content); // This updates the state to hold a function component and to re-render
setModal(true); // This updates a state flag to show the overlay in which the modal is rendered
},
}}>
...
</Provider>
The content of the modal should be dynamic. I was trying to use the state of the component to hold the functional component but I don't think if that's possible or if it's a good practice.
If I understand your question correctly, you're still looking to pass a function from the parent to each child but each child should be able to change the state of a modal component that the parent also has ownership over.
For the above scenario this is something you can do:
const Provider = ({ children, updateModal }) => {
// With this, every child has the ability to call updateModal
return React.Children(children).map(child => cloneElement(child, { updateModal }));
};
const ModalComponent = ({ open, children }) => {
if (!open) return null;
return (
<dialog>
{children}
</dialog>
);
};
const ParentComponent = () => {
const [modal, setModal] = useState(false);
const [modalContent, setModalContent] = useState(null);
const updateModal = (content) => {
setModalContent(content);
setModal(true);
};
return (
<>
<Provider updateModal={updateModal}>
{...insert children here}
</Provider>
<ModalComponent open={modal}>
{modalContent}
</ModalComponent>
</>
);
};

React-Hooks- How to assign an unique "ref" to every rendered item in a renderItem using FlatList?

I am trying to convert a class component to a function component and struggling with assigning the refs to each rendered item in a Flatlist.
This is the original class component.
...
constructor(props) {
super(props);
this.cellRefs = {};
}
....
_renderItem = ({ item }) => {
return (
<Item
ref={ref => {
this.cellRefs[item.id] = ref;
}}
{...item}
/>
);
};
...
Assuming both your Item and the component rendering the FlatList need to be functional components, you need to take care of 2 things
Add dynamic refs to each Item component
Make sure that the Item component uses useImperativeHandle with forwardRef to expose functions
const App = () => {
const cellRefs = useRef({}) // Adding an object as we need more than one ref
const _renderItem = ({ item }) => {
return (
<Item
ref={ref => {
cellRefs.current[item.id] = ref;
}}
{...item}
/>
);
};
....
}
Post that you need to change your Item component like
const Item = React.forwardRef((props, ref) => {
...
const handleClick = () => {};
useImperativeHandle(ref, () => ({
// values that need to accessible by component using ref, Ex
handleClick,
}))
...
})
P.S. If Item is not a functional component, you can avoid the second step
do something like that (from react doc)
function TextInputWithFocusButton() {
const inputEl = useRef(null);
const onButtonClick = () => {
// `current` points to the mounted text input element
inputEl.current.focus();
};
return (
<>
<input ref={inputEl} type="text" />
<button onClick={onButtonClick}>Focus the input</button>
</>
);
}

Categories