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!
Related
I recently started learning react and I got stuck in the above-mentioned error. I know there are plenty of answers to this error. yes, I have seen those solutions but I am not able to map those solutions to my problems. I am pasting my code below please tell me what is wrong with the below code. any help is very much appreciated. thank you.
const orders = () => {
const [stateValue, setState] = useState({
orders: [],
loading: true
});
// getting the orders
useEffect(() => {
axiosInstance.get('/orders.json').then(res => {
transformData(res);
setState({loading: false});
}).catch(err => {
console.log(err)
setState({loading: false});
})
}, []);
// transforming firebase response(objects of objects) into array of objects
const transformData = (response) => {
const ordersData = [];
if(response.data) {
for (let key in response.data) {
ordersData.push({
...response.data[key],
id: key
})
}
}
setState({orders: ordersData});
}
let orders;
orders = stateValue.orders.map((order) => <Order //error line
key={order.id}
ingredients={order.ingredients}
email={order.email}
price={order.price}
/>);
if(stateValue.loading) {
orders = <Loading />
}
return(
<div>
{orders}
</div>
)
}
The setter function of useState hook DOES NOT MERGE STATE like in its class equivalent (it mentioned in the docs):
However, unlike this.setState in a class, updating a state variable always replaces it instead of merging it.
// Merge with prev state in function component
setState(prev => ({...prev, loading: false}))
// In class component, the setter merges state by default
this.setState({loading: false});
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 :)
Here is my component:
class PreCreate extends Component {
handleStartBtn = e => {
this.props.createSurvey(name);
};
componentDidUpdate(prevProps, prevState) {
if (prevProps.currentSurvey !== this.props.currentSurvey) {
const cookies = new Cookies();
cookies.set("assignments", `EHS_${this.props.currentSurvey.uuid}`, {
path: "/"
});
}
this.props.history.push("/polling");
}
render() {
return (
<Button type="primary" onClick={this.handleStartBtn}>
Start
</Button>
);
}
}
const mapStateToProps = state => {
return {
currentSurvey: state.survey.currentSurvey
};
};
const mapDispatchToProps = dispatch => {
return {
createSurvey: name => dispatch(createSurvey(name))
};
};
export default withRouter(
connect(
mapStateToProps ,
mapDispatchToProps
)(PreCreate)
);
createSurvey method is a dispatcher located in my Redux actions its look like below:
export const surveySuccess = survey => {
return {
type: actionType.SURVEY_SUCCESS,
currentSurvey: survey
};
};
export const createSurvey = name => {
return dispatch => {
dispatch(surveyStart());
axios({
method: "post",
data: {
name: name
},
url: `http://127.0.0.1:8000/survey/`,
headers: {
"Content-Type": "application/json"
}
})
.then(res => {
const survey = res.data;
dispatch(surveySuccess(survey));
})
.catch(err => {
console.log(err);
dispatch(surveyFail(err));
});
};
};
My problem is when handleStartBtn method starts to run and createSurvey method is called it goes to createSurvey method which is located in my Redux actions successfully but it seems to the operation related to sending my data by Axis will be postponed until calling componentDidUpdate have been finished. it goes to componentDidUpdate without changing state that i expected to change.It's ridiculous after doing operations located in componentDidUpdate it runs the server side operations and hits Redux states.
I have tried numerous ways to set state but for some reason, the state never gets updated.this is the JSON data that I want my state to change to
export class Provider extends Component {
state = {
posts: [],
profileinfo: {},
dispatch: action => this.setState(state => reducer(state, action))
};
componentDidMount() {
fetch("http://localhost:3001/login").then(response =>
response
.json()
.then(data => this.setState({ profileinfo: data.firstname }))
);
console.log(this.state.profileinfo);
}
render() {
// ...
}
}
setState is asynchronous. Your console log probably triggers before the state got updated. If you want to see the result after the setState call, do it this way:
data => this.setState({ profileinfo: data.firstname }, () => {
console.log(this.state);
});
I've read the docs here but I am having trouble getting the component to rerender after state is updated. The posts are being added, I just have to rerender the component manually to get them to show up, what am I missing?
I have this in the component:
class ListPosts extends Component {
state = {
open: false,
body: '',
id: ''
}
openPostModal = () => this.setState(() => ({
open: true,
}))
closePostModal = () => this.setState(() => ({
open: false,
}))
componentWillMount() {
const selectedCategory = this.props.selectedCategory;
this.props.fetchPosts(selectedCategory);
}
handleChange = (e, value) => {
e.preventDefault();
// console.log('handlechange!', e.target.value)
this.setState({ body: e.target.value });
};
submit = (e) => {
// e.preventDefault();
console.log(this.state.body)
const body = this.state.body;
const id = getUUID()
const category = this.props.selectedCategory;
const post = {
id,
body,
category
}
this.props.dispatch(addPost(post))
this.closePostModal()
}
Then down below I am adding the dispatch to props...
const mapStateToProps = state => ({
posts: state.postsReducer.posts,
loading: state.postsReducer.loading,
error: state.postsReducer.error,
selectedCategory: state.categoriesReducer.selectedCategory,
// selectedPost: state.postsReducer.selectedPost,
});
function mapDispatchToProps (dispatch) {
return {
fetchPosts: (selectedCategory) => dispatch(fetchPosts(selectedCategory)),
addPost: (postObj) => dispatch(addPost(postObj)),
}
}
export default withRouter(connect(
mapStateToProps,
mapDispatchToProps
)(ListPosts))
Here is the code for the reducer:
case C.ADD_POST :
const hasPost = state.some(post => post.id === action.payload.postObj.id)
console.log('caseADD_POST:', action.payload.postObj.id)
return (hasPost) ?
state :
[
...state,
post(null, action)
];