Jest - how to get useSelect data in tesing file - reactjs

I have a component in the React project which uses 2 useSelectors to get data for rendering the page. While testing I am able to cover the UI part but the conditional UI part is not coming on, as useSelector data is not replicatable via mock. Can someone help me.
const MyComponent = () => {
const { id } = useSelector(selecetdId);
const dataArray = useSelector(data);
return (
<div>
{!id ? (
<Fragment>
<AnotherCustomComponent />
{dataArray.length > 0 ? **<CustomTable dataArray={filteredSuppliers} />** : <NoData />}
</Fragment>
) : (
**<div className="supplier-details">
<CustomHeader />
</div>**
)}
</div>
);
};
As i dont have hold on the output from the useSelector id, dataArray, In testing i am not able to cover starred part of code

I have a feeling you are using ReactTestingLibrary and redux with dedux-toolkit ;)
You need to do two things:
Have a mocked state, with a values that can result in the condition you want to test
Render the component within redux provider
Quick sample
const renderComponent(props, reduxState) {
const reduxStore = configureStore({ reducer: {} /**/, preLoadedSate: reduxState });
return render(<Provider store={reduxStore}><MyComponentToBeTested ...props /></Provider>))
}
it("Should show something if id in the state is undefined", () => {
const stateSoThatUseSelectorWillReturnIdUndefined = { john: true };
const renderedResult = renderComponent({}, stateSoThatUseSelectorWillReturnIdUndefined);
/// expect something is rendered or not
});
More details here: redux state unit testing

Related

How to delay React from rendering child component until parent component finish fetching?

I have a parent component that fetch from webAPI and will return an array of people.
I also have a child component that suppose to receive props from parent, in which props is obtained from the fetch
The problem I am having is it seems react renders the child component before the fetch is completed.
Here is my code:
import React from "react";
import { Person} from "./Components";
export function Body({departmentId}){
const [peopleArray,setPeopleArray] = React.useState([]);
React.useEffect(
() => {
const getPeopleArr = async () => {
const response = await
fetch(`https://xxxx`);
const data = await response.json();
setPeopleArray(data);
};
getPeopleArr();
}, []);
return(
<div>
<Person name={peopleArray[0].personalDetails.name} position={peopleArray[0].department.name}/>
</div>
);
}
as you can see from above, initially peopleArray is an empty array. and react will give me the error "Uncaught TypeError: Cannot read properties of undefined (reading 'personalDetails')"
if I remove the code<Person name={peopleArray[0].personalDetails.name} position={peopleArray[0].department.name}/>
wait a bit until it fetched, and then quickly paste the code, it displays the Person component successfully.
I attempted to add SetTimeOut in the child component and try to delay it for few seconds, by doing:
setTimeout(() => {
}, "2000")
but this doesn't seem to help the issue.
Could you please suggest smthg that I can do to delay the child component from rendering before the fetch completed?
Thank you
Edit:
one thing I can think of was to create multiple useState(s) for each props I want to pass to the child, for example
const [name,SetName] = data[0].personalDetails.name;
...
<Person name = {name}>
but then if I have 20 props to pass, then I have to create 20 useState(s)
EDIT: The ? works... thanks guys :D
There are several ways to solve this problem.
The simplest is to use optional chaining. Something like this:
<Person name={peopleArray[0]?.personalDetails?.name} position={peopleArray[0]?.department?.name}/>
If peopleArray[0] is undefined, it won't render anything
Another solution is to use the length:
peopleArray.length > 0 && <Person name={peopleArray[0].personalDetails.name} position={peopleArray[0].department.name}/>
The third one is to introduce a loading state, which can be true by default, and you can change it to false after the fetch promise has been resolved
you should check first if the variable is not empty:
{peopleArray.length && <Person name={peopleArray[0]?.personalDetails.name} position={peopleArray[0]?.department.name}/> }
just introduce a state like "loaded" and then render your child component conditionally:
loaded && <ChildComponent />
In your case:
import React from "react";
import { Person} from "./Components";
export function Body({departmentId}){
const [peopleArray,setPeopleArray] = React.useState([]);
const [loaded, setLoaded] = React.useState(false);
React.useEffect(
() => {
const getPeopleArr = async () => {
const response = await
fetch(`https://xxxx`);
const data = await response.json();
setPeopleArray(data);
};
await getPeopleArr();
setLoaded(true);
}, []);
return(
<div>
{loaded && <Person name={peopleArray[0].personalDetails.name} position={peopleArray[0].department.name}/>}
</div>
);
}
You can add "?" after peopleArray[0] to avoid error:
<Person name={peopleArray[0]?.personalDetails.name} position={peopleArray[0]?.department.name}/>
Or you can use conditional render:
<div>
{peopleArray[0] ? <Person name={peopleArray[0].personalDetails.name}
position={peopleArray[0].department.name}/> : null }
</div>
Quick fix, is just to check the array's length:
return peopleArray.length ? <Person name={peopleArray[0].personalDetails.name} position={peopleArray[0].department.name}/> : <></>
Though it doesn't consider the case when fetched result is empty array

How do you rerender a React component when an object's property is updated?

I have an object in my React application which is getting updated from another system. I'd like to display the properties of this object in a component, such that it updates in real time.
The object is tracked in my state manager, Jotai. I know that Jotai will only re-render my component when the actual object itself changes, not its properties. I'm not sure if that is possible.
Here is a sample that demonstrates my issue:
import React from "react";
import { Provider, atom, useAtom } from "jotai";
const myObject = { number: 0 };
const myObjectAtom = atom(myObject);
const myObjectPropertyAtom = atom((get) => {
const obj = get(myObjectAtom)
return obj.number
});
const ObjectDisplay = () => {
const [myObject] = useAtom(myObjectAtom);
const [myObjectProperty] = useAtom(myObjectPropertyAtom);
const forceUpdate = React.useState()[1].bind(null, {});
return (
<div>
{/* This doesn't update when the object updates */}
<p>{myObject.number}</p>
{/* This doesn't seem to work at all. */}
<p>{myObjectProperty}</p>
{/* I know you shouldn't do this, its just for demo */}
<button onClick={forceUpdate}>Force Update</button>
</div>
);
};
const App = () => {
// Update the object's property
setInterval(() => {
myObject.number += 0.1;
}, 100);
return (
<Provider>
<ObjectDisplay />
</Provider>
);
};
export default App;
Sandbox
you can use useEffect for this.
useEffect(()=> {
// code
}, [myObject.number])

React Context value gets updated, but component doesn't re-render

This Codesandbox only has mobile styles as of now
I currently have a list of items being rendered based on their status.
Goal: When the user clicks on a nav button inside the modal, it updates the status type in context. Another component called SuggestionList consumes the context via useContext and renders out the items that are set to the new status.
Problem: The value in context is definitely being updated, but the SuggestionList component consuming the context is not re-rendering with a new list of items based on the status from context.
This seems to be a common problem:
Does new React Context API trigger re-renders?
React Context api - Consumer Does Not re-render after context changed
Component not re rendering when value from useContext is updated
I've tried a lot of suggestions from different posts, but I just cannot figure out why my SuggestionList component is not re-rendering upon value change in context. I'm hoping someone can give me some insight.
Context.js
// CONTEXT.JS
import { useState, createContext } from 'react';
export const RenderTypeContext = createContext();
export const RenderTypeProvider = ({ children }) => {
const [type, setType] = useState('suggestion');
const renderControls = {
type,
setType,
};
console.log(type); // logs out the new value, but does not cause a re-render in the SuggestionList component
return (
<RenderTypeContext.Provider value={renderControls}>
{children}
</RenderTypeContext.Provider>
);
};
SuggestionPage.jsx
// SuggestionPage.jsx
export const SuggestionsPage = () => {
return (
<>
<Header />
<FeedbackBar />
<RenderTypeProvider>
<SuggestionList />
</RenderTypeProvider>
</>
);
};
SuggestionList.jsx
// SuggestionList.jsx
import { RenderTypeContext } from '../../../../components/MobileModal/context';
export const SuggestionList = () => {
const retrievedRequests = useContext(RequestsContext);
const renderType = useContext(RenderTypeContext);
const { type } = renderType;
const renderedRequests = retrievedRequests.filter((req) => req.status === type);
return (
<main className={styles.container}>
{!renderedRequests.length && <EmptySuggestion />}
{renderedRequests.length &&
renderedRequests.map((request) => (
<Suggestion request={request} key={request.title} />
))}
</main>
);
};
Button.jsx
// Button.jsx
import { RenderTypeContext } from './context';
export const Button = ({ handleClick, activeButton, index, title }) => {
const tabRef = useRef();
const renderType = useContext(RenderTypeContext);
const { setType } = renderType;
useEffect(() => {
if (index === 0) {
tabRef.current.focus();
}
}, [index]);
return (
<button
className={`${styles.buttons} ${
activeButton === index && styles.activeButton
}`}
onClick={() => {
setType('planned');
handleClick(index);
}}
ref={index === 0 ? tabRef : null}
tabIndex="0"
>
{title}
</button>
);
};
Thanks
After a good night's rest, I finally solved it. It's amazing what you can miss when you're tired.
I didn't realize that I was placing the same provider as a child of itself. Once I removed the child provider, which was nested within itself, and raised the "parent" provider up the tree a little bit, everything started working.
So the issue wasn't that the component consuming the context wasn't updating, it was that my placement of providers was conflicting with each other. I lost track of my component tree. Dumb mistake.
The moral of the story, being tired can make you not see solutions. Get rest.

Data from useRef renders only after changing the code and saving it

I'm having an issue with my react app. I retrieve data from my elasticsearch server and trying to display it on the website.
const RecipesPage = (props: Props) => {
const recipes = useRef<Recipe[]>([]);
const avCategories = ['meats', 'pastas', 'vegan', 'seafood', 'desserts', 'all'];
const currentCategory = props.match.params.category_name.toLowerCase();
useEffect(() => {
const recipesReq = getRecipesByCategory(currentCategory);
recipesReq
.then((data) => recipes.current = data.hits.hits)
}, [currentCategory])
if (avCategories.includes(currentCategory)) {
return (
<div>
<Navbar />
<ul style={{marginTop: "5.5rem"}}>{recipes.current.map((recipe: Recipe) => <p key={recipe._id}>{recipe._source.recipe_name}</p>)}</ul>
</div>
);
} else {
return (
<div>
<Navbar />
<p style={{marginTop: "5.5rem"}}>No category named {currentCategory}</p>
</div>
);
}
};
export default RecipesPage
The problem is that when I'm trying to display the data it shows up only after saving the code and then after refreshing the page it's gone. I guess it's a problem related to useRef hook, but I'm not sure.
You should use state if you need the component to rerender.
When using useEffect, you shouldn't pass an array or object reference as a dependency. React uses referential comparison to check for changes, which means the useEffect hook will run every time a new object/array is created regardless if the actual data changes, which can cause an infinite render loop:
https://www.benmvp.com/blog/object-array-dependencies-react-useEffect-hook/

React hooks - Dispatching a reset state from parent component

This is my application with the scenario reproduced, here the demo in codesandbox
I have two components, Leagues ( parent ) and Details ( Child ).
I have a implemented reset button example in the Details Component button which does
const cleanArray = () => {
setDataHomeTeam([]);
};
<button onClick={cleanValue} type="button">Reset</button>
You can see in the demo that is emptying out an array of a team stat
My question is, can i implement the same button but out from Details component and from the parent component Leagues for example? Whats the way to achieve it?
I thought to go this way but i can not get it done.
So in my Details.js
let Details = forwardRef(({ ....
const cleanArray = () => {
setDataHomeTeam([]);
};
useImperativeHandle(ref, () => {
return {
cleanValue: cleanValue
}
});
in App.js
<Leagues/>
<button onClick={cleanValue} type="button">Reset</button>
<Details ref={ref} />
I get this error : 'cleanValue' is not defined no-undef
is it something that i can not do with react? How can i achieve it?
I think your approach sounds correct except for lacking the way of calling the api cleanValue you exposed. Basically you have to call it via a ref you pass to it as following:
function App() {
const ref = useRef();
return (
<>
<Leagues />
{/* call it via ref */}
<button onClick={() => ref.current.cleanValue()} type="button">Reset</button>
<Details ref={ref} />
</>
)
}
Codesandbox link: https://codesandbox.io/s/nifty-raman-c0zff?file=/src/components/Details.js
I don't completely understand what you are trying to do, but here is a solution I think is going to work for your problem
let's say you wanna filter that array with the selected team which is liverpool, first if you have control over the incoming data I recommend changing the obj in the array likethis
{day : 16 , teamName:"liverpool"}, this is going to help you filter that array later,
then you useEffect & useState to update that array
[teams, setTeams] = useState([]);
// the array changes here {day: 1 , teamName : "sao paulo"} , {day:2 ,teamname:"liverpool"}]
useEffect(()=>{
setTeams((T)=>{
return T.filter((oneTeam)=>{
return oneTeam.teamName == selectedTeam;
});
})
},[teams,selectedTeam]);

Resources