How to display comments coming from Redux store on individual component - reactjs

I have created a basic single page app, on initial page there is some dummy data and on click of each item I direct user to individual details page of that item. I wanted to implement comment and delete comment functionality which I successfully did but now when I comment or delete the comment it doesn't only happen at that individual page but in every other page too. Please see the sandbox example for better clarify.
https://codesandbox.io/s/objective-feistel-g62g0?file=/src/components/ProductDetails.js
So once you add some comments in individual page, go back and then click to another products, apparently you will see that the comments you've done in other pages are also available there. What do you think causing this problem ?

The same state being reused by all the different pages.
Try to load dynamically load reducers for each page/router differently to use distinct state values.
You can start from here
Redux modules and code splitting

I found my own logical solution. You probably might find a better solution but this works pretty well too. I thought of passing another property in the object with the params I get from url and then filter the comments by their url params. So that I could do filtering based on the url parameters and display the comments only made on that specific page.
So ProductDetails.js page should be looking like this:
import React, { useState, useEffect } from 'react';
import { Input, Button } from 'semantic-ui-react'
import { connect } from 'react-redux';
const ProductDetails = (props) => {
const [commentObject, setCommentObject] = useState({
text: "",
date: "",
id: ""
});
const clickHandler = () => {
if (!commentObject.text.trim()) {
return
}
props.addNewComment(commentObject)
setCommentObject({
...commentObject,
text: ""
})
console.log(commentObject.id);
}
useEffect(() => {
}, []);
return (
<div>
{props.posts ? props.posts.text : null}
{props.comments.filter(comment => {
return comment.postId === props.match.params.slug
}).map(({ text, id }) => {
return (<div key={id}>
<p>{text}</p>
<Button onClick={() => props.deleteComment(id)} >Delete comment</Button></div>)
})}
<Input value={commentObject.text}
onChange={comment => setCommentObject({ text: comment.target.value, date: new Date(), id: Date.now(), postId: props.match.params.slug })}
/>
<Button onClick={clickHandler} >Add comment</Button>
</div>
);
}
const mapStateToProps = (state, ownProps) => {
let slug = ownProps.match.params.slug;
return {
...state,
posts: state.posts.find(post => post.slug === slug),
}
}
const mapDispatchToProps = (dispatch) => {
return {
addNewComment: (object) => { dispatch({ type: "ADD_COMMENT", payload: { comment: { text: object.text, date: object.date, id: object.id, postId: object.postId } } }) },
deleteComment: (id) => { dispatch({ type: "DELETE_COMMENT", id: id }) }
}
}
export default connect(mapStateToProps, mapDispatchToProps)(ProductDetails);

Related

How do I add some state to redux state when try to write unit test in jest/RTL?

I wanted to start testing with Redux-toolkit, according to the article I found.
https://redux.js.org/usage/writing-tests#setting-up
The right practice is to write integration test. But right now I want to test a sign out button which is controlled by authstate,in order to set it's value I have to sign in first. What I want to do is I can give some state to the authstate in the test file instead of having to login. So I can actually write unit test on my sign out button.
Here's the code and the test
const Navbar = () => {
const cart = useAppSelector((state) => state.cart);
const user = useAppSelector((state) => state.auth);
const dispatch = useAppDispatch();
const handleLogout = () => {
localStorage.removeItem("persist:root");
window.location.reload();
};
return(
{user.user !== null ? (
<>
<MenuItem>Hello {user.user?.username} </MenuItem>{" "}
<ExitToApp
style={{ cursor: "pointer", marginLeft: "10px" }}
onClick={() => handleLogout()}
/>
<Link to="/order">
<MenuItem>Order</MenuItem>
</Link>
</>
) : (
<>
<Link to="/register">
<MenuItem>REGISTER</MenuItem>
</Link>
<Link to="/login">
<MenuItem>SIGN IN</MenuItem>
</Link>
</>
)}
)
Authslice
const slice = createSlice({
name: "auth",
initialState: { user: null, token: null } as {
user: null | UserDataInterface;
token: null | string;
},
reducers: {
setCredentials: (state,{ payload: { user, token } }: PayloadAction<{ user: UserDataInterface; token: string }>) => {
state.user = user;
state.token = token;
}
},
extraReducers: (builder) => {}
});
test file
test("When click on logoutk,it will trigger handle logout", () => {
//TODO: should let the user state to not be empty first
await store.dispatch(setCredentials())
//TODO: then we can track if logout label exist
//TODO: click on logout button and mock localstorage maybe ?
});
What should I do with this kind of unit test, if it involves prerequisites for redux-state ?
After some research, I found out how to do this. It might not be the best practice. But I think it could be useful in a lot of scenario if you don't want to write integration test.
test("When click on logout,it will trigger handle logout", async () => {
//TODO: should let the user state to not be empty first
store.dispatch(
setCredentials({
user: {
username: "Kai",
_id: "efekfjefke",
email: "dfdkdfkdf@gmail.com",
createdAt: new Date(2013, 13, 1),
updatedAt: new Date(2013, 13, 1),
img: "223232",
},
token: "test12345",
})
);
//TODO: then we can track if logout label exist
await waitFor(() =>
expect(screen.queryByRole("userTitle")).toBeInTheDocument()
);
await waitFor(() =>
expect(screen.queryByTestId("logout")).toBeInTheDocument()
);
//TODO: click on logout button and mock localstorage maybe ?
const userLogout = screen.getByTestId("logout");
fireEvent.click(userLogout);
// should not be able to query the userTitle since it's logout
await waitFor(() =>
expect(screen.queryByRole("userTitle")).not.toBeInTheDocument()
);
});
I found out you can directly dispatch state through the store. which is really convenient.
Cause for me. What's tricky is I don't know how to write integration test across two components or pages
Right now if I can directly dispatch some state first. I can unit-test the function in single page or components to see if it does what I needed.
I am pretty new to testing, so if there's any better approach at this, please let me know!
Hope this can help someone who is struggling!!

i could not edit data by using react-redux

I have an issue with update new data by using react-redux, Add and remove are working fine, but it will return null object when i want to edit one of the data.
I am not sure what stage cause wrong.
action.users.js
import { v4 as uuid } from 'uuid';
// ADD_USER
export const addUser = ({ username = '', location = '' } = {}) => ({
type: 'ADD_USER',
user: { id: uuid(), username, location },
});
//REMOVE_USER
export const removeUser = ({ id } = {}) => ({ type: 'REMOVE_USER', id });
//EDIT_USER
export const editUser = ({ id, updates } = {}) => ({
type: 'EDIT_USER',
id,
updates,
});
components.EditUserPage.js
import React from 'react';
import { connect } from 'react-redux';
import UserForm from './UserForm';
import { editUser } from '../actions/users';
const EditUserPage = props => {
return (
<div>
<UserForm
user={props.user}
onSubmit={user => {
props.dispatch(editUser(props.user.id, user));
props.history.push('/Users');
}}
/>
</div>
);
};
const mapStateToProps = (state, props) => {
return {
user: state.user.find(user => user.id === props.match.params.id),
};
};
export default connect(mapStateToProps)(EditUserPage);
reducers.users.js
const usersReducerDefaultState = [];
export default (state = usersReducerDefaultState, action) => {
switch (action.type) {
case 'ADD_USER':
return [...state, action.user];
case 'REMOVE_USER':
return state.filter(({ id }) => id !== action.id);
case 'EDIT_USER':
return state.map(user => {
if (user.id === action.id) {
return {
...user,
...action.updates,
};
} else {
return user;
}
});
default:
return state;
}
};
The page should go to ./edit when i click one of the data and the input value will show the currently selected data in the userform component. it seems like going well at this stage, i change the input value and click the create button, the page back to /User, unfortunately, the selected data return null object. please help me. you answer will help me to jump out of this nightmare.
Change
props.dispatch(editUser(props.user.id, user));
to
props.dispatch(editUser({id: props.user.id, updates: user}));
You define the method signature of edit user as
function editUser ({ id, updates } = {}) {…}
That is, a function that takes one (optional) argument. That argument is expected to be an object with an id and updates property. (I don't think you should make the argument optional. It is needed for the rest of the function to work.)
However, you call the function with two arguments, presumable a number and an object. Also, if props.user.id ends up being undefined, then it will be replaced with the default value of {}, and id and updates will be undefined, but no error will occur (which is what you want, because you are passing the wrong type to the function).
Alternatively, you could define the method signature to take to positional arguments and not change the function call:
const editUser = (id, updates) => (…)
or, if your UserForm component includes id in the call to onSubmit:
const editUser = ({ id, ...updates }) => (…) // use destructuring to select the id from the argument
// call it like this:
props.dispatch(editUser({user}));

Make a common function to store the local storage data

I am a newbie in react-native. I have a folder structure like below:
-screens
-page1.js
-page2.js
-page3.js
-page4.js
-App.js
In page1.js, I have a function to store data to localStorage
let obj = {
name: 'John Doe',
email: 'test#email.com',
city: 'Singapore'
}
AsyncStorage.setItem('user', JSON.stringify(obj));
Now I have to display these data in few of my other pages. This is my code.
class Page2 extends Component {
state = {
username: false
};
async componentDidMount() {
const usernameGet = await AsyncStorage.getItem('user');
let parsed = JSON.parse(usernameGet);
if (parsed) {
this.setState({
username: parsed.name,
email: parsed.email
});
} else {
this.setState({
username: false,
email: false
});
}
}
render() {
return (
<View style={styles.container}>
<Text style={styles.saved}>
{this.state.username}
</Text>
</View>
);
}
}
export default Page2;
This is how I display data in page2. I may need to show these in other page too.
I dont want to repeat these codes in each page.
Any suggestions how to do it in react-native?
You can extract the data you need to display into it's own component and re-use it in any page that you need to display it in.
Another option is to use a higher-order component, that way you can wrap it around any components that need the data and it'll be passed down as a prop.
You can make your Constant.js where you can put all your common required utils and constants, reusable anywhere n your app.
In your Constant.js:
export const USER_DATA = {
set: ({ user}) => {
localStorage.setItem('user', JSON.stringify(obj));
},
remove: () => {
localStorage.removeItem('user');
localStorage.removeItem('refresh_token');
},
get: () => ({
user: localStorage.getItem('user'),
}),
}
in your any component, you can import it and use like this :
import { USER_DATA } from './Constants';
let user = {
name: 'John Doe',
email: 'test#email.com',
city: 'Singapore'
}
// set LocalStorage
USER_DATA.set(user);
// get LocalStorage
USER_DATA.get().user
That's you can make Constant common file and reuse them anywhere to avoid writing redundant code.
Simplified Reusable approach of localStorage
export const localData = {
add(key, value) {
localStorage.setItem(key, JSON.stringify(value));
},
remove(key, value) {
localStorage.removeItem(key);
},
load(key) {
const stored = localStorage.getItem(key);
return stored == null ? undefined : JSON.parse(stored);
},
};
localData.add("user_name", "serialCoder")
console.log( "After set 👉", localData.load("user_name") )
localData.remove("user_name")
console.log( "After remove 👉", localData.load("user_name") )

Optional field inside Options React Select

Hey guys im trying to create a autosuggestion in cooperation with redux-form. Im using the Creatable approach. I loading my options via an external API. The problem is, i need a extra field in every Option Object. {value: "test#gmx.de", label: "test#gmx.de", dn:"CN...." }. Is there a possibility to do so?
I typically add my own properties inside the callback for the API request, just before setting the options in the state. For example...
axios.get('/some/api/request')
.then(response => {
const options = response.data.map(item => {
// Add whatever custom properties you want here
return ({value: "test#gmx.de", label: "test#gmx.de", dn:"CN...." })
})
// set your options in the state to the new options constant from above
dispatch(change('formName', 'options', options))
Hope this helps!
//Handle change with either selectedOption
handleChange(selectedOption){
this.setState({ selectedOption })
if(this.props.onOptionSelect){
this.props.onOptionSelect(selectedOption.data)
}
}
loadOptions(input, callback) {
this.props.loadOptions(input).then(options => {
callback(null, {options: options})
})
}
render() {
const {selectedOption} = this.state
const selectClass = this.props.meta.touched && this.props.meta.error ? "has-error form-group" : "form-group"
return (
<div className={selectClass}>
<AsyncCreatable
value={selectedOption}
onChange={this.handleChange}
loadOptions={this.loadOptions}
isLoading={false}
placeholder={this.props.label}
promptTextCreator={(label) => this.props.promtLabel(label)}
onBlur={() => this.props.input.onBlur(selectedOption.value || "")}
/>
</div>
)
}
//Function to convert incomming users in usable options (React Select)
export const convertADUsersToOptions = users => {
return users.map(user => {
return {
value: normalizeDN(user.dn),
label: user.mail
}
})
}

SetState of an array of Objects in React

Ok, so I'm so frustrated finding the right solution so I'm posting the problem here. Giving an answer would help me a lot, coz I'm stuck!
the state tree looks like this
this.state = {
itemList : [{
_id : 1234,
description : 'This the description',
amount : 100
}, {
_id : 1234,
description : 'This the description',
amount : 100
}],
}
The problems are :
can not update any specific key in the Object of the array according
to the _id
The previous state should remain intact
answered March 25 2018
This is how you would use setState and prevstate to update a certain attribute of an object in your data structure.
this.setState(prevState => ({
itemList: prevState.itemList.map(
obj => (obj._id === 1234 ? Object.assign(obj, { description: "New Description" }) : obj)
)
}));
answered Dec 12 2019 (REACT HOOKS)
import React, { useState } from 'react';
const App = () => {
const [data, setData] = useState([
{
username: '141451',
password: 'password',
favoriteFood: 'pizza',
},
{
username: '15151',
password: '91jf7jn38f8jn3',
favoriteFood: 'beans'
}
]);
return (
<div>
{data.map(user => {
return (
<div onClick={() => {
setData([...data].map(object => {
if(object.username === user.username) {
return {
...object,
favoriteFood: 'Potatos',
someNewRandomAttribute: 'X'
}
}
else return object;
}))
}}>
{JSON.stringify(user) + '\n'}
</div>
)
})}
</div>
)
}
to update state constructed like this you will have to find index of element you want to update, copy the array and change found index.
it's easier and more readable if you keep list of records as object, with id as a key and record as a value.
The only way to do this will be to copy itemList, modify it, and set the state to it.
update() {
let itemList = this.state.itemList.slice();
//update it
this.setState({ itemList });
}
Best way to update data into an array of objects
onChange={ (e) => this.setState({formData: { ...this.state.formData, 'plan_id': e.target.value}})}

Resources