Life cycle transform in hooks for a functional component - reactjs

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 :)

Related

React - update state with timeout in reducer

Can anyone help me to update state with timeout in react reducer.
I don't have much experience even with pure javascript, so I can hardly find an answer myself at this moment.
In my first ever react app (with useContex and useReducer) i have simple BUTTON checkbox with onClick function to dispatch type in reducer:
<ToggleButton
className="mb-2"
id="Getdocs"
type="checkbox"
variant="outline-secondary"
size="sm"
checked={Getdocs}
onChange={(e) => Getdocsaction()}
>
Render documents
</ToggleButton>
In my context.js i have:
import React, { useContext, useReducer} from 'react'
import reducer from './reducer'
const AppContext = React.createContext()
const initialState = {
.
.
.
Showdocs: false,
.
.
.
}
const AppProvider = ({ children }) => {
const [state, dispatch] = useReducer(reducer, initialState)
...
const Getdocsaction = () => {
dispatch({ type: 'GET_DOCS' })
}
...
return (
<AppContext.Provider
value={{
...state,
Getdocsaction
}}
>
{children}
</AppContext.Provider>
)
}
export const useGlobalContext = () => {
return useContext(AppContext)
}
export { AppContext, AppProvider }
In reducer.js i have:
const reducer = (state, action) => {
if (action.type === 'GET_DOCS') {
let newPassports = state.oldDocs.filter((doc) => doc.passport === true);
if (newPassports.length === 0) {
state.Passports = []
state.Showdocs = true
state.Getdocs = false /uncheck checkbox button
setTimeout(() => {
state.Showdocs = false //wont update this
console.log("setTimeout fired") //logs in console after 5 secs
}, 5000)
return { ...state }
}
if (newPassports.length !== 0) {
return { ...state, Passports: newPassports, Showdocs: true, Getdocs: !state.Getdocs }
}
return { ...state }
}
throw new Error('no matching action type')
}
export default reducer
Finally, in my App.js i check if Showdocs is true or false and return the rest (return the passports from updated array or bootstrap alert if there is no values in array (Passport.length === 0) )
What i am trying to achieve is that when i have empty Passports array i want set Showdocs: true (in order to show alert msg) and set it back to false after 5 secs (in order to remove msg ...)
Any help is welcome and even keywords by which i could research this issue.
Thank you.
Reducers are intended to be “pure” and synchronous, and they shouldn't mutate input arguments. Since mutating state after a delay is a side-effect, you should consider instead handling this in a useEffect hook separately.
E.g.:
const SomeComponent = () => {
const [state, dispatch] = useReducer(reducer)
const { hideDocsAfterDelay } = state
useEffect(() => {
if (!hideDocsAfterDelay) return
const timer = setTimeout(() => {
dispatch({ TYPE: "HIDE_DOCS" })
}, 5000)
return () => { clearTimeout(timer) }
}, [hideDocsAfterDelay])
// …
}
In this scenario, you would set a hideDocsAfterDelay property in your state to trigger the timer and another action handler that would set showDocs and hideDocsAfterDelay to false.
I think you should implement an action that basically updates the state with this state.Showdocs = false and then dispatch this action inside a setTimeout.
So basically change Getdocsaction to this:
const Getdocsaction = () => {
dispatch({ type: 'GET_DOCS' })
setTimeout(() => {dispatch({type: 'The action that sets Showdocs to false'})}, 5000);
}

code using useParams, useEffect and useState hooks

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]);

state can not set in functional component

i send dataSource parameter to flowing functional component, dataSource has data but chartOptions state can not set.
thanks...
import React, { useEffect, useState } from "react";
const Trend = ({ dataSource }) => {
const [chartOptions, setChartOptions] = useState({
series: {
data: dataSource.map(x => {
return ["TEST1", "TEST2"];
})
}
});
console.log(chartOptions);
return (
<div>
<h1>TEST</h1>
</div>
);
};
export default Trend;
You should set it as this, as it sets state before the dataSource arrives.
Try using useEffect and set the state there like
useEffect(() => {
const data = dataSource.map(x => {
return ["TEST1", "TEST2"];
});
setChartOptions(
series: {
data: data
}
);
},[dataSource]);
To calculate the value of your own state from a prop you should use useEffect and include this prop in the hook useEffect within the dependency array so that whenever it changes the value of the state is updated.
Yo can see it in the React documentation, useEffect hook
This could be an implementation:
import React, { useEffect, useState } from "react";
const App = ({ dataSource }) => {
const [chartOptions, setChartOptions] = useState({});
useEffect(() => {
setChartOptions({
series: {
data: dataSource.map(x => {
return ["I'm ", "test2"];
})
}
});
}, [dataSource]);
return (
<div>
<h1>
{chartOptions.series &&
chartOptions.series.data.map(chartOption => <div>{chartOption}</div>)}
</h1>
</div>
);
};
export default App;
Here's an example
PD: If you want a more extensive explanation about useEffect (it is quite complex) and where you will solve doubts about updating the state through props, etc I attach an article by one of the developers of React that I think is very interesting.
Lazy initial state -
If the initial state is the result of an expensive computation, you may provide a function instead, which will be executed only on the initial render:
const [state, setState] = useState(() => {
const initialState = {
series: {
data: dataSource.map(x => {
return ["TEST1", "TEST2"];
})
}
}
return initialState;
});

Setting variable with ReactJS's useState hook does not work

I'm trying to set a variable with useState after an API call, but it doesn't work. Debugging by reactotron, he makes the API call, but he doesn't set the variable.
export default function Forecast({ navigation }) {
const [cityData, setCityData] = useState([]);
const idNavigation = navigation.state.params.cityData.woeid;
async function loadCityData(cityID) {
const response = await api.get(`${cityID}`);
setCityData([response]);
console.tron.log(cityData);
}
useEffect(() => {
if (idNavigation) {
loadCityData(idNavigation);
}
return () => {};
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [idNavigation]);
return <Text>Forecast Weather</Text>;
}
Forecast.propTypes = {
navigation: PropTypes.shape({
state: PropTypes.object,
}).isRequired,
};
Setting state in React is async for the most part and the changes to the state might not be visible if you try to console.log them right away. The recommended way to do this with hooks, is to check for the updated state in useEffect:
async function loadCityData(cityID) {
const response = await api.get(`${cityID}`);
setCityData([response]);
}
// Track cityData and log the changes to it
useEffect(() => {
console.log(cityData);
}, [cityData]);
// Rest of the code
// ...
Umm, I think it maybe reactotron or api problem.
Just try
const [cityData, setCityData] = useState('foo');
...
return <Text>{JSON.stringify(cityData)}</Text>;
If your plobrem came from reactron, then you can see the response from API.
because useState is asynchronous function.
setCityData([response]); // asynchronous function so not set the new data to state yet.
console.tron.log(cityData);// so you get the old data.
See this Example
const Forecast = ({ idNavigation }) => {
const [cityData, setCityData] = React.useState([]);
function loadCityData(cityID) {
setTimeout(() => {
setCityData([1,2,3,4,5]);
console.log("this is old data", cityID, cityData); // because useState is asynchronous function
}, 2000);
}
React.useEffect(() => {
if (idNavigation) {
console.log("load ", idNavigation);
loadCityData(idNavigation);
}
}, [idNavigation]);
React.useEffect(() => {
if(cityData.length > 0) {
console.log("this is new data", cityData);
}
}, [cityData]); // when cityData changed and component mounted, this function called.
return <div>Forecast Weather</div>;
}
class App extends React.Component {
state = {
id: 1,
}
componentDidMount() {
setTimeout(() => {
this.setState({id: 2});
}, 2000);
}
render() {
return (
<div>
<Forecast idNavigation={this.state.id}/>
</div>
);
}
}
ReactDOM.render( <App / > , document.getElementById('root'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.7.0-alpha.2/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.7.0-alpha.2/umd/react-dom.production.min.js"></script>
<div id="root"></div>
Solution
if you use Class Component you can use callback of setState() function.
but if you use Functional Component you can't use a callback.
so you should use useEffect() to solve this problem.

How do I rewrite this with 'recompose'?

I have this class component and want to rewrite it to stateless functional component with recompose:
export default class Popular extends Component {
state = {
value: 0,
selected: "All",
repos: null
}
componentDidMount() {
this.handleSelected(this.state.selected)
}
handleChange = (e, value) => {
this.setState({ value })
}
handleSelected = lang => {
this.setState({
selected: lang,
repos: null
})
fetchPopularRepos(lang).then(repos => {
this.setState({
selected: lang,
repos
})
})
}
I'm struggling to combine onSelectLanguage and onFetchRepos in one function as in my code before refactoring. I don't know how to rewrite this for my componentDidMount function as well.
UPDATE:
got this working with:
const enhance = compose(
withStateHandlers(initialState, {
onChangeLanguage,
onSelectLanguage
}),
lifecycle({
componentDidMount() {
fetchPopularRepos(this.props.selected).then(repos => {
this.setState({
repos
})
})
}
}),
lifecycle({
componentDidUpdate(prevProps) {
if (this.props.selected !== prevProps.selected) {
this.setState({ repos: null })
fetchPopularRepos(this.props.selected).then(repos => {
this.setState({
repos
})
})
}
}
})
)
These lifecycles don't look very sexy though. Not sure if this worth refactoring.
This looks like a case where you'd want to use the lifecycle() method. I'm not a recompose expert, however I think the following adjustments might achieve what you're after:
const onFetchPopularRepos = props => () => {
// Make sure the method returns the promise
return fetchPopularRepos(props.selected).then(repos => ({
repos: repos
}))
}
const withPopularReposState = withStateHandlers(initialState, {
onChangeLanguage,
onSelectLanguage
})
// Add a life cycle hook to fetch data on mount
const withLifecycle = lifecycle({
componentDidMount() {
// Update the state
onFetchPopularRepos(this.props).then(newState => this.setState(newState))
}
})();
// Compose the functional component with both lifecycle HOC
const enhance = withLifecycle(withPopularReposState)
Extending on the previous answer, you could use functional composition to combine the onFetchRepos and onSelectLanguage as required.
If I understand your requirements correctly, you should be able to achieve this by the following:
const initialState = {
value: 0,
selected: "All",
repos: null
}
const onChangeLanguage = props => (event, value) => ({
value
})
const onSelectLanguage = props => lang => ({
selected: lang
})
const onFetchRepos = props => (fetchPopularRepos(props.selected).then(repos => ({
repos
})))
// Combined function: onFetchRepos followed by call to onSelectLanguage
const onFetchReposWithSelectLanguage = props => onFetchRepos(props)
.then(response => props.onSelectLanguage(response))
// Minimal code to compose a functional component with both state handers and
// lifecycle handlers
const enhance = compose(
withStateHandlers(initialState, {
onChangeLanguage,
onSelectLanguage
}),
lifecycle({
componentDidMount() {
// Fetch repos and select lang on mount
onFetchReposWithSelectLanguage(this.props)
}
})
)
Update
// Minimal code to compose a functional component with both state handers and
// lifecycle handlers
const enhance = compose(
withStateHandlers(initialState, {
onChangeLanguage,
onSelectLanguage,
setRepos
}),
lifecycle({
componentDidMount() {
// Reuse onSelectLanguage logic and call setState manually, use setState callback that
// fires after state is updated to trigger fetch based on newState.selected language
this.setState(onSelectLanguage(this.props.selected)(this.props), newState => {
fetchPopularRepos(newState.selected).then(repos => {
this.setState({
repos
})
})
})
}
})
)
Hope this helps you!

Resources