How do I clean up form using recompose in reactjs? - reactjs

After form submit I want to clean it but this solution doesn't seem to work. here's my submit handler:
handleSubmit: ({ title, body }, props) => e => {
e.preventDefault()
props
.handleCreatePost({
variables: {
title,
body
}
})
.then(() => {
return {
title: "",
body: ""
}
})
.catch(err => console.log(err))
}

Every time you need to change props from inside of your component you have to use withStateHandlers.
compose(
withStateHandlers(
({title, body})=> ({title, body}), //set the state from parent props
{
setTitle: () => title => ({title}), // update the title
setBody: () => body => ({body}), // update the body
clearProps: () => () => ({titel:'', body: ''}) // create a handler to reset the values
}
),
withHandlers({
handleSubmit: ({ title, body, clearProps }, props) => e => {
e.preventDefault()
props
.handleCreatePost({
variables: {
title,
body
}
})
.then(clearProps) // reset the values
.catch(err => console.log(err))
}
)
)

Related

props change does not re-render child component

im passing a variable and two functions that changes the state of the variable as props in a child component. when i execute the functions the variable changes its state but the child component does not re-render, knowing that im using the same code in another class that calls the same child component and its working fine.
Here's the functions and the render of the child component.
onRowClickHandle = async (product) => {
BlockTimer.execute(() => {
this.props.onViewProductScreen({ product });
}, 1000);
};
async componentDidMount(){
await this.fetchReadLaterBooks();
}
async fetchReadLaterBooks(){
const user = await AsyncStorage.getItem('username');
const isLoggedIn = await AsyncStorage.getItem('isLoggedIn');
if (isLoggedIn == 1) {
await fetch(Config.backendAPI+`/readlater.php?username=${user}&test=1&select`)
.then((response) => {
return response.json();
})
.then((json) => {
if(json.length != this.state.prodList.length){
json.map((product, index) => {
this.state.prodList.push(product.id)
});
this.setState({
prodList:this.state.prodList,
isLoading:false,
});
}
this.forceUpdate();
})
.catch((error) => alert(error));
}
}
removeReadLater = async (id) => {
const user = await AsyncStorage.getItem('username');
this.setState({
prodList:this.state.prodList.filter((productId) => productId !== id),
});
await fetch(Config.backendAPI+`/readlater.php?username=${user}&idDoc=${id}&delete`)
.then((response) => response.json())
.catch((error) => alert(error));
}
addReadLater = async (id) =>{
try{
const user = await AsyncStorage.getItem('username');
//insertion dans la liste actuelle des readlater.
const joined = this.state.prodList.concat(id);
this.setState({
prodList:joined,
});
//insertion dans la base
await fetch(Config.backendAPI+`/readlater.php?username=${user}&idDoc=${id}&insert`)
.then((response) => response.json())
.catch((er) => alert(er));
}catch(error){
console.log(error);
}
};
renderItem = ({ item }) => {
return (
<ProdList
addReadLater={this.addReadLater}
removeReadLater={this.removeReadLater}
readLaterBooks={this.state.prodList}
item={item}
onRowClickHandle={this.onRowClickHandle}
/>
);
};
render() {
const {
theme: {
colors: { background, text,
dark: isDark },
},
} = this.props;
if(!this.state.isLoading){
return (
<View style={{flex:1 ,backgroundColor:background}}>
<FlatList
data={this.props.products}
renderItem={this.state.prodList ? this.renderItem : null}
/>
</View>
);
}else{
return <LogoSpinner fullStretch />;
}
}
}

How can I make React wait for a Redux action before render?

Im trying to make my own Blog App but it doesn't seem to work as expected. Here are my codes for my "startSetMyBlogs":
export const startSetMyBlogs = () => {
return (dispatch, getState) => {
const myBlogs = [];
const blogRef = database.ref('blogs/')
const uid = getState().auth.uid;
//join table to get blogs match with logging in user
return database.ref(`author-blog`)
.once('value', snap => snap.val())
.then(childSnapshot => {
childSnapshot.forEach((blog) => {
if (blog.val() == uid) {
blogRef.child(blog.key).once('value').then(blogSnapshot => {
myBlogs.push({
id: blogSnapshot.key,
...blogSnapshot.val()
})
})
}
})
// Dispatch another action to set redux state.
dispatch(setUserBlogs(myBlogs));
})
}
}
my "setUserBlogs" action:
export const setUserBlogs = (myBlogs) => ({
type: 'SET_USER_BLOGS',
myBlogs
})
So how can I wait for "setUserBlogs" action to finish before passing props to my BlogList component ?
All you need to do is to store and pass on a loading state until your data is ready
export const startSetMyBlogs = () => {
return (dispatch, getState) => {
const myBlogs = [];
const blogRef = database.ref('blogs/')
const uid = getState().auth.uid;
//join table to get blogs match with logging in user
dispatch({type: 'SET_LOADING', payload: true});
return database.ref(`author-blog`)
.once('value', snap => snap.val())
.then(childSnapshot => {
childSnapshot.forEach((blog) => {
if (blog.val() == uid) {
blogRef.child(blog.key).once('value').then(blogSnapshot => {
myBlogs.push({
id: blogSnapshot.key,
...blogSnapshot.val()
})
})
}
})
// Dispatch another action to set redux state.
dispatch(setUserBlogs(myBlogs));
dispatch({type: 'SET_LOADING', payload: false});
})
}
}
Once you do that you can use this loading state in the component to render a loader
const mapStateToProps = state => {
blogs: state.blogs,
isLoading: state.isLoading
}
Use it as below: Dispatch an action once you get the as you want it from Firebase.
return database.ref(`author-blog`)
.once('value', snap => snap.val())
.then(childSnapshot => {
childSnapshot.forEach((blog) => {
if (blog.val() == uid) {
blogRef.child(blog.key).once('value').then(blogSnapshot => {
myBlogs.push({
id: blogSnapshot.key,
...blogSnapshot.val()
})
})
}
})
dispatch(setUserBlogs(myBlogs));
})
NOTE: API call is async so you have to wait for the data to assign it to state.
Hope this helps.

How to Handle Error using try catch using React State with Arrow functions

I am new to React, I am trying to implement some error validation with a react arrow function but got no luck all day.
The catch works and I can print the errors, but I dont know how to link the errors I am printing inside errorsHandler() to the other const where the form is for styling and warnings.
const errorsHandler= fieldErrors => {
if (fieldErrors) {
fieldErrors.forEach(err => {
const errorFieldName= err.field;
const errorDescription = err.message;
console.log('Field', errorFieldName, 'Description', errorDescription);
// Field name Description name already exists
});
}
};
export const defaultGroupModel = {
description: '',
active: true,
name: '',
}
const GroupFormModal = ({ editId, editGroup, onSave}) => {
const [groupState, setGroupState] = useState(defaultGroupModel);
useEffect(() => {
if (editGroup) {
setGroupState(editGroup);
}
}, [editGroup]);
const handleChange = ({ target: { value, name} }) => {
setGroupState({ ...groupState, [name]: value });
};
return ( (...) <Form onSubmit={e => onSave(e, groupState)} onReset={onReset}> (...);
};
const mapDispatchToProps = dispatch => ({
onSave: (e, group) => {
e.preventDefault();
if (group.id) {
dispatch(updateGroup(group));
} else {
dispatch(createGroup(group)).catch(error => {
errorsHandler(error.data.fieldErrors);
});
}
},
});
export default connect(
mapStateToProps,
mapDispatchToProps,
)(GroupFormModal);
I have tried to create an [errorState, setErrorState] and use useEffect inside the errorsHandler but get Invalid Hook. How can I have the handle inside the catch to be in the same context as the form ?
Thank you in advance
There are few things you can do here. First, use mapDispatchToProps to wrap inside dispatch your action's creators (without then and catch)
const mapDispatchToProps = dispatch =>({
updateGroup : group => dispatch(updateGroup(group)),
createGroup : group => dispatch(createGroup(group))
})
Now you can set an internal state to reflect those errors
const Component = ({ updateGroup, createGroup }) =>{
const [errors, setErrors] = useState(false)
const onSave = (group,e) =>{
createGroup(group)
.then(res => console.log('everything ok'))
.catch(err => setError(err) /* now you have the errors inside your component*/)
}
return <form onSubmit={ e => onSave(group,e)) }> /*...*/ </form>
}

Not updating the state instantly

So, when I click on the add to cart button on the Screen2, it logs articleToCart aka cartArticle as empty array... Only when I go back to Screen1 and than again on the Screen2, pressing add to cart button again it logs cartArticle array with one item even though add to cart button was clicked 2x... How can I make it that when I click on add to cart button, it updates the state immediately? What am I doing wrong? I am using react navigation v2. Is it possible to setState trough params and that to be instant not like this, with delay?
class Screen1 extends Component {
state = {
articles: {
article: [],
},
cartArticle: []
};
articleToCart = () => {
this.setState(prevState => {
return {
cartArticle: prevState.cartArticle.concat(prevState.articles.article)
};
});
};
qrCodeOnReadHandler = ({ data }) => {
fetch(data)
.then(response => response.json())
.then(json => [
console.log(json),
this.setState({
...this.state,
articles: {
...this.state.articles,
article: json[0],
}
}),
this.props.navigation.navigate("Screen2", {
addToCartOnPress: () => this.articleToCart(),
articleToCart: this.state.cartArticle,
})
])
.catch(err => {
alert("Nesto ne valja. Pokusajte ponovo!");
console.log(err);
});
};
render() {
return (
);
}
}
Second screen
class Screen2 extends Component {
addToCartHandler = () => {
const { navigation } = this.props;
const articleToCart =navigation.getParam("articleToCart","Nedostupno");
const add = navigation.getParam("addToCartOnPress", "Nedostupno");
console.log(articleToCart);
add();
};
goBackHandler = () => {
this.props.navigation.goBack();
};
render() {
return (
<View style={styles.buttons}>
<CustomButton color="#1DA1F2" onPress={this.goBackHandler}>
Back
</CustomButton>
<CustomButton color="#1DA1F2" onPress={this.addToCartHandler}>
Add to Cart
</CustomButton>
);
}
}
in your qrCodeOnReadHandler on screen1:
[
console.log(json),
this.setState({
...this.state,
articles: {
...this.state.articles,
article: json[0],
}
}),
this.props.navigation.navigate("Screen2", {
addToCartOnPress: () => this.articleToCart(),
articleToCart: this.state.cartArticle,
})
]
you are returning an array with your functions as its indices.
try changing it to this instead.
{
console.log(json);
this.setState({
...this.state,
articles: {
...this.state.articles,
article: json[0],
}
});
this.props.navigation.navigate("Screen2", {
addToCartOnPress: () => this.articleToCart(),
articleToCart: this.state.cartArticle,
})
}
setState is async, you can't read values just after setting them .. but you can use setState callback, sth. like this:
.then(json => {
console.log(json)
this.setState({
...this.state,
articles: {
...this.state.articles,
article: json[0],
}
}, () => {
this.props.navigation.navigate("Screen2", {
addToCartOnPress: () => this.articleToCart(),
articleToCart: this.state.cartArticle,
})
})
})

React Redux not re-rendering, but state being copied?

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

Resources