react-testing-library test child component set data in parent component - reactjs

I am new to react-testing-library, in my project I have a parent-child component like this, where one of parent's states are set by passing a function as props to child component, and then called inside child component. I am just wondering if there is a way to mock that returned state from child component using react-testing-lib and jest? Thanks for all suggestions and ideas!
const Parent = () => {
const [state, setState] = useState(undefined);
let getState= (state) => setState(state);
return (
// Something else...
<Child getState={getState}/>
)
}
const Child = ({getState}) => {
const fetchData = () => {
fetch(`someapi`)
.then(res => res.json())
.then(data =>
// Do some other stuff
getState(data);
})
}
useEffect(() => {
fetchData();
);
return (
//render something else
)
}

useEffect snippet is not looking well. first of all you cant use fetchData because of this func. is private. you could use Child().
useEffect(() => {
Child();
},[]);

Related

Children components are not re rendered on forceUpdate() in React

I have a callback method that calls a forceUpdate() hook in parent. I expect this to re render and call ChildA with updated props. These values are updated in another component say ChildB.
While I keep a debugger at callback method, I see updated values for props and Im getting a hit to return method as well. But the child component is not getting hit at all.
const Body = FC =>{
const [state, setState] = useState<any>();
const forceUpdate = useForceUpdate();
const update = useCallback(() => forceUpdate(), []);
return (
//able to see updated state here when update() is called
//but execution is not going inside ChildA
//even use effects on updated state are not getting triggered
<ChildA
state = {state}
/>
<ChildB
update = {update}
/>
)
}
Existing hook:
const reducer = (state: boolean, _action: null): boolean => !state;
export const useForceUpdate = () => {
const [, dispatch] = useReducer(reducer, true);
// Turn dispatch(required_parameter) into dispatch().
const memoizedDispatch = useMemo(
() => () => {
dispatch(null);
},
[dispatch]
);
return memoizedDispatch;
};
When I changed the existing hook into the below format, this worked for me.
function useForceUpdate() {
const [, forceUpdate] = useReducer(x => x + 1, 0);
const memoizedUpdate = useMemo(
() => () => {
forceUpdate(0);
},
[forceUpdate]
);
return memoizedUpdate;
};

React functional components async props to children

I have a functional component (App.js) where I want to fetch some initial data using useEffect.
useEffect(() => {
const init = async () => {
const posts = await getPosts(0, 3);
const newArticles = await getArticles(posts);
setArticles(() => [...articles, ...newArticles]);
};
init();
}, []);
then I want to pass the result to a child
<ArticleList articles={articles}></ArticleList>
but in the Article component I get an empty array when I try to console.log the props.
useEffect(() => {
console.log(props.articles);
setArticles(() => props.articles);
}, [props.articles]);
How can I solve this issue?

React functional component re-renders with old data

I have this functional component that is used as a child component. Like this:
//... in the ParentComponent
<ChildComponent
id={somevalue}
/>
and the Child Component looks like this:
const ChildComponent = ({
id
}) => {
const [rawData, setRawData] = useState([]);
const [processedData, setProcessedData] = useState([]);
useEffect(() => {
// ... do some time consuming calculations
SetProcessedData(data);
}, [rawData]);
useEffect(() => {
console.log('useEffect');
// fetch data with axios and ...
() => {
const reloadData = async () => {
axios.request({
url,
data,
...config,
})
.then((result) => {
SetRawData(data);
})
};
reloadData();
}, [id]);
console.log('Render');
return(
{processedData.map( ....) }
);
}
The first time the ChildComponent is render everything works like charm.
The problem occurs when somevalue and the prop (id) is changed.
The child sees the new props and rerenders the component AND calls useEffect[id]
The problem is that the render happens BEFORE the useEffect so the processedData state is still old.
I tried to avoid the first rerender by setting a isLoading variable but the first place I can do it is the useEffect[id] which is called AFTER the rerender.
So the console shows:
Render
useEffect
What am I missing?

Error when initializing the child component

I am going crazy with useEffect. I started coding 3 months ago and I am not really experienced.
I have a parent component which initializes some data from database with useEffect then I pass that data as props to a child component which initializes some other data from database with useEffect. I can't make it work no matter what I try. I think that the reason is because component unmounts before the child component's initialization is done. I have been reading documentations but couldn't figure out how to overcome this problem.
I appreciate if you can help me solve it.
...
import ModelDesigner from './subComponents/ModelDesigner'
const Model = ({ match }) => {
const [model, setModel] = useState({})
const initialize = async (id) => {
try {
const res = await axios.get(`/model/${id}`)
setModel(res.data)
} catch (err) {
console.log(err.response.data)
}
}
const link = match.params.link
useEffect(() => {
initialize(link)
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [])
return (
<ModelDesigner user={model.user} />
)
...
import PropTypes from 'prop-types'
const ModelDesigner = ({ user }) => {
const [profile, setProfile] = useState({})
const loadProfile = async (id) => {
try {
const profile = await axios.get(`/profile/id/${id}`)
setProfile(profile.data)
} catch (err) {
console.log(err.response.data)
}
}
useEffect(() => {
loadProfile(user)
}, [])
return ( ... )
I think I see what's going on here. Model is initialized asynchronously, so when it's first rendered, the model variable is an empty object.
However, the child ModelDesigner is rendered right away and its useEffect callback runs before the model is loaded from the API. So it's probably calling /profile/id/undefined instead of using the profile ID you want.
One way to fix this is to wait on rendering the child component until you've finished loading the data it needs for its props. For this example I guess that would look something like:
return model.user
? <ModelDesigner user={model.user} />
: <div />
Alternatively, you could add user as a dependency for useEffect and skip the API call if it's an empty value. That way it'll wait to try to initialize until it has the ID it needs.
const loadProfile = async (id) => {
if (!id) {
return;
}
try {
const profile = await axios.get(`/profile/id/${id}`)
setProfile(profile.data)
} catch (err) {
console.log(err.response.data)
}
}
useEffect(() => {
loadProfile(user)
}, [user])

Hooks in react are not updating my state correctly

I'm trying to load some data which I get from an API in a form, but I seem to be doing something wrong with my state hook.
In the code below I'm using hooks to define an employee and employeeId.
After that I'm trying to use useEffect to mimic the componentDidMount function from a class component.
Once in here I check if there are params in the url and I update the employeeId state with setEmployeeId(props.match.params.employeeId).
The issue is, my state value didn't update and my whole flow collapses.
Try to keep in mind that I rather use function components for this.
export default function EmployeeDetail(props) {
const [employeeId, setEmployeeId] = useState<number>(-1);
const [isLoading, setIsLoading] = useState(false);
const [employee, setEmployee] = useState<IEmployee>();
useEffect(() => componentDidMount(), []);
const componentDidMount = () => {
// --> I get the correct id from the params
if (props.match.params && props.match.params.employeeId) {
setEmployeeId(props.match.params.employeeId)
}
// This remains -1, while it should be the params.employeeId
if (employeeId) {
getEmployee();
}
}
const getEmployee = () => {
setIsLoading(true);
EmployeeService.getEmployee(employeeId) // --> This will return an invalid employee
.then((response) => setEmployee(response.data))
.catch((err: any) => console.log(err))
.finally(() => setIsLoading(false))
}
return (
<div>
...
</div>
)
}
The new value from setEmployeeId will be available probably in the next render.
The code you're running is part of the same render so the value won't be set yet.
Since you're in the same function, use the value you already have: props.match.params.employeeId.
Remember, when you call set* you're instructing React to queue an update. The update may happen when React decides.
If you'd prefer your getEmployee to only run once currentEmployeeId changes, consider putting that in its own effect:
useEffect(() => {
getEmployee(currentEmployeeId);
}, [currentEmployeeId])
The problem seems to be that you are trying to use the "updated" state before it is updated. I suggest you to use something like
export default function EmployeeDetail(props) {
const [employeeId, setEmployeeId] = useState<number>(-1);
const [isLoading, setIsLoading] = useState(false);
const [employee, setEmployee] = useState<IEmployee>();
useEffect(() => componentDidMount(), []);
const componentDidMount = () => {
// --> I get the correct id from the params
let currentEmployeeId
if (props.match.params && props.match.params.employeeId) {
currentEmployeeId = props.match.params.employeeId
setEmployeeId(currentEmployeeId)
}
// This was remaining -1, because state wasn't updated
if (currentEmployeeId) {
getEmployee(currentEmployeeId);
//It's a good practice to only change the value gotten from a
//function by changing its parameter
}
}
const getEmployee = (id: number) => {
setIsLoading(true);
EmployeeService.getEmployee(id)
.then((response) => setEmployee(response.data))
.catch((err: any) => console.log(err))
.finally(() => setIsLoading(false))
}
return (
<div>
...
</div>
)
}
The function returned from useEffect will be called on onmount. Since you're using implicit return, that's what happens in your case. If you need it to be called on mount, you need to call it instead of returning.
Edit: since you also set employee id, you need to track in the dependency array. This is due to the fact that setting state is async in React and the updated state value will be available only on the next render.
useEffect(() => {
componentDidMount()
}, [employeeId]);
An alternative would be to use the data from props directly in the getEmployee method:
useEffect(() => {
componentDidMount()
}, []);
const componentDidMount = () => {
if (props.match.params && props.match.params.employeeId) {
setEmployeeId(props.match.params.employeeId)
getEmployee(props.match.params.employeeId);
}
}
const getEmployee = (employeeId) => {
setIsLoading(true);
EmployeeService.getEmployee(employeeId);
.then((response) => setEmployee(response.data))
.catch((err: any) => console.log(err))
.finally(() => setIsLoading(false))
}

Resources