Can someone please tell me the equivalent code using hooks for the following:
componentDidMount() {
const { match: { params } } = this.props;
axios.get(`/api/users/${params.userId}`)
.then(({ data: user }) => {
console.log('user', user);
this.setState({ user });
});
}
The exact functionality to match your class component into a functional component with hooks would be the following:
import * as React from "react";
import { useParams } from "react-router-dom";
const Component = () => {
const { userId } = useParams();
const [state, setState] = React.useState({ user: null });
React.useEffect(() => {
axios.get(`/api/users/${userId}`)
.then(({ data: user }) => {
console.log('user', user);
setState({ user });
});
}, []);
}
React.useEffect(() => {}, []) with an empty dependency array essentially works the same way as the componentDidMount lifecycle method.
The React.useState hook returns an array with the state and a method to update the state setState.
References:
https://reactjs.org/docs/hooks-state.html
https://reactjs.org/docs/hooks-effect.html
As an aside, and pointed out by #Yoshi:
The snippet provided is error prone, and the "moving to hooks" snippet will have the same errors that occur in the example. For example, as the request is in componentDidMount, if the userId changes it won't trigger a fetch to get the user data for the userId. To ensure this works in the hook, all you need to do is provide the userId in the dependency array in the useEffect...
const latestRequest = React.useRef(null);
React.useEffect(() => {
latestRequest.current = userId;
axios.get(`/api/users/${userId}`)
.then(({ data: user }) => {
if (latestRequest.current == userId) {
setState({ user });
}
});
}, [userId]);
Related
Thanks everyone, especially Mr.Drew Reese. If you are newbie as me, see his answer.
I don't know why but when I console log state data if I use useEffect, it always rerender although state generalInfo not change :/ so someone can help me to fix it and explain my wrong?
I want the result which is the data will be updated when generalInfo changes.
Thanks so much!
This is my useEffect
======================== Problem in here:
const {onGetGeneralInfo, generalInfo} = props;
const [data, setData] = useState(generalInfo);
useEffect(() => {
onGetGeneralInfo();
setData(generalInfo);
}, [generalInfo]);
======================== fix:
useEffect(() => {
onGetGeneralInfo();
}, []);
useEffect(() => {
setData(generalInfo);
}, [generalInfo, setData]);
this is mapStateToProps
const mapStateToProps = state => {
const {general} = state;
return {
generalInfo: general.generalInfo,
};
};
this is mapDispatchToProps
const mapDispatchToProps = dispatch => {
return {
onGetGeneralInfo: bindActionCreators(getGeneralInfo, dispatch),
};
};
this is reducer
case GET_GENERAL_INFO_SUCCESS: {
const {payload} = action;
return {
...state,
generalInfo: payload,
};
}
this is action
export function getGeneralInfo(data) {
return {
type: GET_GENERAL_INFO,
payload: data,
};
}
export function getGeneralInfoSuccess(data) {
return {
type: GET_GENERAL_INFO_SUCCESS,
payload: data,
};
}
export function getGeneralInfoFail(data) {
return {
type: GET_GENERAL_INFO_FAIL,
payload: data,
};
}
and this is saga
export function* getGeneralInfoSaga() {
try {
const tokenKey = yield AsyncStorage.getItem('tokenKey');
const userId = yield AsyncStorage.getItem('userId');
const params = {
method: 'GET',
headers: {
Authorization: `Bearer ${tokenKey}`,
},
};
const response = yield call(
fetch,
`${API_GET_GENERAL_INFO}?id=${userId}`,
params,
);
const body = yield call([response, response.json]);
if (response.status === 200) {
yield put(getGeneralInfoSuccess(body));
} else {
yield put(getGeneralInfoFail());
throw new Error(response);
}
} catch (error) {
yield put(getGeneralInfoFail());
console.log(error);
}
}
the initial state in redux and state in component is an empty array.
so I want to GET data from API. and I push it to redux's state. then I
useState it. I want to use useEffect because I want to update state
when I PUT the data and update local state after update.
Ok, so I've gathered that you want fetch the data when the component mounts, and then store the fetched data into local state when it is populated. For this you will want to separate out the concerns into individual effect hooks. One to dispatch the data fetch once when the component mounts, the other to "listen" for changes to the redux state to update the local state. Note that it is generally considered anti-pattern to store passed props in local state.
const {onGetGeneralInfo, generalInfo} = props;
const [data, setData] = useState(generalInfo);
// fetch data on mount
useEffect(() => {
onGetGeneralInfo();
}, []);
// Update local state when `generalInfo` updates.
useEffect(() => {
setData(generalInfo);
}, [generalInfo, setData]);
in your useEfect you are setting generalInfo and it causes change in the dependency array of useEffect. So, it runs over and over:
useEffect(() => {
onGetGeneralInfo();
setData(generalInfo);
}, [generalInfo]);
try this instead:
useEffect(() => {
onGetGeneralInfo();
setData(generalInfo); // or try to remove it if it is unnecessary based on below question.
}, []);
However, I don't understand why you have used setData(generalInfo); in useEffect when you have set it before. does it change in onGetGeneralInfo(); function?
Yow hook has or uses things that are not listed in the dependencies list
useEffect(() => {
onGetGeneralInfo();
setData(generalInfo);
}, [ onGetGeneralInfo, setData, generalInfo]);
Also let's remember that useEffect is call before the component mounts and after it mounts, so if you add a log it will be printed
I'm learning react query and the following code is working as expecting but I have this warning message:
React Hook useEffect has missing dependencies: 'code' and 'mutate'. Either include them or remove the dependency array.eslintreact-hooks/exhaustive-deps
But if I add 'code' and 'mutate' in the dependency array I have an infinite loop.
import React, { useState, useEffect } from "react";
import { useMutation } from "react-query";
import * as api from "../api/api";
const getQuery = () => {
const queryParams = new URLSearchParams(window.location.search);
return queryParams.get("code");
};
const Authentication = () => {
const [code] = useState(getQuery());
useEffect(() => {
if (code) {
mutate.mutate(code);
}
}, []);
const auth = async () => {
window.location.href = `https://www.betaseries.com/authorize?client_id=${process.env.REACT_APP_API_KEY}&scope=&redirect_uri=${process.env.REACT_APP_API_URL_CALLBACK}`;
};
const mutate = useMutation(api.access_token, {
onSuccess: (data) => {
localStorage.setItem("isAuth", data.data.access_token);
},
});
return <button onClick={auth}>Login</button>;
};
export default Authentication;
Short explanation of what I did:
User click on login button, he is redirected to the website to enter his login / password
const auth = async () => {
window.location.href = `https://www.betaseries.com/authorize?client_id=${process.env.REACT_APP_API_KEY}&scope=&redirect_uri=${process.env.REACT_APP_API_URL_CALLBACK}`;
};
After a success login he is redirected to my website with a url params ?code=xxx
I catch the code and use it to call a route that will provide me his access_token
useEffect(() => {
if (code) {
mutate.mutate(code);
}
}, []);
The mutate function itself is stable, but the object returned from useMutation is not. If you destruct, you can add it to your dependency array:
const { mutate } = useMutation(…)
You can directly pass code and mutate on the dependence array since your useEffect is depending on the change in variable code and mutate object it self. here is what you can try:
useEffect(() => {
if (code) {
mutate.mutate(code);
}
}, [code, mutate]);
export default function Main({ match }) {
const userid = match.params.id;
const [user, setUser] = useState([]);
async function fetchuser() {
const response = await api.get('/emps/profile', {
headers: {
userid: match.params.id,
},
});
setUser(response.data);
console.log(response.data);
console.log(user);
}
useEffect(() => {
fetchuser();
}, [match.params.id]);
In the above code the response.data is written into console but user state is empty. Can someone tell me why this is?
Two suggestions:
Determine if response contains any data before logging it.
Move your fetchData function into the useEffect hook. https://stackoverflow.com/a/56851963/8943092
Below is an example of how you can test for the existence of data, and here is a live Sandbox.
Note that we use a simple conditional to check if (myData) is truthy. Our useState hook sets no default value, so the conditional returns true once data is present.
In the render method, we use a ternary to check for the existence of data.
Your solution may be slightly different because you set the default value of user to an empty array []. Assuming your API call returns an array, you'll test for data with if (user.length > 0).
import React, { useEffect, useState } from "react";
export default function App() {
const [myData, setMyData] = useState();
useEffect(() => {
function fetchData() {
setTimeout(function () {
setMyData("I am user data");
}, 3000);
}
if (myData) {
console.log(myData);
} else {
console.log("No data yet");
}
fetchData();
}, [myData]);
return (
<div className="App">{myData ? <p>{myData}</p> : <p>No data yet</p>}</div>
);
}
I'm having trouble understanding how to write a test for a hook without the following warning when using renderHook from "#testing-library/react-hooks".
"Warning: An update to TestHook inside a test was not wrapped in act(...)."
Basically the hook sets initial value in state using useState and then within a useEffect hook I do something asynchronously which ends up updating the state value.
import React from "react";
// fake request
const fetchData = () => Promise.resolve("data");
export const useGetData = () => {
const initialData = { state: "loading" };
const [data, setData] = React.useState(initialData);
React.useEffect(() => {
fetchData()
.then(() => setData({ state: "loaded" }));
}, []);
return data;
};
The hook simply returns the state value at all times.. so I've written a test to assert that it returns the initial value at first and eventually returns the new state value.
import { renderHook } from "#testing-library/react-hooks";
import { useGetData } from "./useGetData";
describe("useGetData", async () => {
it('Should initially return an object with state as "loading"', () => {
const { result } = renderHook(() => useGetData());
expect(result.current).toEqual({ state: "loading" });
});
it('Should eventually return an object with state as "loaded"', async () => {
const { result, waitForNextUpdate } = renderHook(() => useGetData());
await waitForNextUpdate();
expect(result.current).toEqual({ state: "loaded" });
});
});
I've created a sandbox that replicates this:
https://codesandbox.io/s/dazzling-faraday-ht4cd?file=/src/useGetData.test.ts
I've looked into what this warning means and what act is.. but for this particular scenario I'm not sure whats missing.
You can fix it by doing this:
await act(async () => {
await waitForNextUpdate();
});
You need to wrap any function that's going to update the state by the act function
I have this componentWillReceiveProps life cycle in my code and I want to write it for a functional component. As I saw, this is possible only with React Hooks. The problem is I did not understood the very well and I need some help.
So, how would be this written in a functional component?
I saw some examples, but not exactly like this case.
componentWillReceiveProps = (newProps) => {
const apiData = newProps.apiData;
if (apiData.articles) {
this.setState(() => ({
pageLoading: false,
articles: apiData.articles.articles,
}), () => {
//this.filterDisplayedArticles()
})
} else if (apiData.articleSearch && apiData.articleSearch.success) {
let articles = apiData.articleSearch.articles;
this.setState(() => ({
pageLoading: false,
articles: articles
}))
}
}
you can use useState hook for state management and componentwillrecieveprops,didmount,and willmount for useEffect hook lets see below code for functional component
import React,{useState,useEffect} from 'react'
const App =() =>{
const [pageLoading,setLoading] = useState(false)
const [articles,setarticles] = useState([])
useEffect((newProps) => {
const apiData = newProps.apiData;
if (apiData.articles) {
setLoading(false)
setarticles(apiData.articles.articles)
} else if (apiData.articleSearch && apiData.articleSearch.success) {
let articles = apiData.articleSearch.articles;
setLoading(false)
setarticles(articles)
}
}, [pageLoading,articles])
return (
....child
)
}
export default App
you can use the useEffect hook here to apply a change based on the parameters needed
And use the useState hook to track your state
import React, { useEffect, useState } from "react";
function DoSomething({ apiData }) {
const { articles, articleSearch } = apiData;
const { state, setState } = useState({ pageLoading: true, articles: [] });
useEffect(() => {
if (articles) {
setState({
pageLoading: false,
articles: apiData.articles.articles
});
} else if (articleSearch && articleSearch.success) {
setState({
pageLoading: false,
articles: articleSearch.articles
});
}
}, [articles, articleSearch]);
return <div>I'm {state.pageLoading ? "loading" : "done loading"}</div>;
}
Play with it live :)