I'm trying to pass a nested object within an object within a reducer.
im telling redux to pass like along with the ...post. However it's giving me
like is not defined.
Reducer
const initialState = {
post: [],
postError: null,
posts:[],
isEditing:false,
isEditingId:null,
likes:0,
postId:null
}
case ADD_LIKE:
console.log(action.id) // renders post id which is 2
console.log(state.posts) // logs posts array
console.log(state.posts)
return {
...state,
posts: state.posts.map(post => {
if (post.id === action.id) {
post.Likes.map( (like) => {
console.log(like); // renders like log
})
}
return {
...post,
...like, /// need to pass the like here but it says is not defined
Likes: like.Likes + 1
}
})
};
console.log(like); // renders this
Like count are being called here
<Like like={id} likes={Likes.length} />
I think you have a problem inside the return.
From the code, Here where you initialize the like variable (inside the map).
post.Likes.map( (like) => {
console.log(like); // renders like log
})
So, the scope of like will not exist outside of it.
But, you are trying to access it outside in return.
return {
...post,
...like,
Likes: like.Likes + 1
}
Edited
If I understand your expectation correct.
Here the solution.
return {
...post,
...post.Likes,
Likes: post.Likes.length + 1
}
Related
This question already has answers here:
How do I return the response from an asynchronous call?
(41 answers)
Closed last year.
I am trying to read a value from RealtimeDatabase on Firebase and render it in an element, but it keeps returning undefined. I have the following code:
const getStudentName = (studentId) => {
firebase.database().ref('students').child(studentId).on("value", (snapshot) => {
return snapshot.val().name;
})
}
const StudentName = (studentId) => ( <p>{getStudentName(studentId)}</p> )
I know it's nothing wrong with the database itself or the value I'm finding, because if I do:
const getStudentName = (studentId) => {
firebase.database().ref('students').child(studentId).on("value", (snapshot) => {
console.log(snapshot.val().name);
return "Test";
})
}
I still see a correct name from my database outputted to console as expected, yet "Test" is not returned to the element. However, if I do it like this:
const getStudentName = (studentId) => {
firebase.database().ref('students').child(studentId).on("value", (snapshot) => {
console.log(snapshot.val().name);
})
return "Test";
}
then "Test" is returned to the element and displayed. I'm very confused, as I don't understand how my console.log() can be reached inside the function but a 'return' statement right after it will not return.
New to React and Firebase, please help! Thank you.
EDIT: I'm sure it's self-explanatory, but you can assume a simple database in the form:
{ "students": [
"0": { "name": "David" },
"1": { "name": "Sally" } ]}
If 'studentId' is 0 then 'console.log(snapshot.val().name)' successfully outputs 'David', but 'David' will not return to the element.
You can't return something from an asynchronous call like that. If you check in the debugger or add some logging, you'll see that your outer return "Test" runs before the console.log(snapshot.val().name) is ever called.
Instead in React you'll want to use a useState hook (or setState method) to tell React about the new value, so that it can then rerender the UI.
I recommend reading the React documentation on the using the state hook, and the documentation on setState.
I'm not sure where you are consuming getStudentName, but your current code makes attaches a real-time listener to that database location. Each time the data at that location updates, your callback function gets invoked. Because of that, returning a value from such a function doesn't make much sense.
If you instead meant to fetch the name from the database just once, you can use the once() method, which returns a Promise containing the value you are looking for.
As another small optimization, if you only need the student's name, consider fetching /students/{studentId}/name instead.
const getStudentName = (studentId) => {
return firebase.database()
.ref("students")
.child(studentId)
.child("name")
.once("value")
.then(nameSnapshot => nameSnapshot.val());
}
With the above code, getStudentName(studentId) now returns a Promise<string | null>, where null would be returned when that student doesn't exist.
getStudentName(studentId)
.then(studentName => { /* ... do something ... */ })
.catch(err => { /* ... handle errors ... */ })
If instead you were filling a <Student> component, continuing to use the on snapshot listener may be the better choice:
const Student = (props) => {
const [studentInfo, setStudentInfo] = useState({ status: "loading", data: null, error: null });
useEffect(() => {
// build reference
const studentDataRef = firebase.database()
.ref("students")
.child(props.studentId)
.child("name");
// attach listener
const listener = studentDataRef.on(
'value',
(snapshot) => {
setStudentInfo({
status: "ready",
data: snapshot.val(),
error: null
});
},
(error) => {
setStudentInfo({
status: "error",
data: null,
error
});
}
);
// detach listener in unsubscribe callback
return () => studentDataRef.off(listener);
}, [props.studentId]); // <- run above code whenever props.studentId changes
// handle the different states while the data is loading
switch (studentInfo.status) {
case "loading":
return null; // hides component, could also show a placeholder/spinner
case "error":
return (
<div class="error">
Failed to retrieve data: {studentInfo.error.message}
</div>
);
}
// render data using studentInfo.data
return (
<div id={"student-" + props.studentId}>
<img src={studentInfo.data.image} />
<span>{studentInfo.data.name}</span>
</div>
);
}
Because of how often you might end up using that above useState/useEffect combo, you could rewrite it into your own useDatabaseData hook.
I'm trying to build a React/Redux app and I am a beginner. I want to use the React Sortable HOC to have a rearrangeable list but I cannot get the new arrangement to stick.
I have a functional component where I get the list of items. The item structure is like this:
items [ {name, courseList}, {name, courseList}, ...].
To populate the table I make an api call and update the prop variable using MapStateToProps. Here's a bit of code:
function CoursesPage({
student,
studentCourses,
loadStudentCourses,
updateCoursesOrder,
...props
}) {
useEffect(() => {
if (studentCourses.length === 0) {
loadStudentCourses(student.id).catch((error) => {
alert("Loading student courses failed" + error);
});
}
}, []);
...
}
and this is the mapStateToProps function:
function mapStateToProps(state) {
return {
student: state.student,
studentCourses: state.studentCourses,
};
}
This bit works fine, and everything appears.
The problem is when I try to rearrange and save it in onSortEnd function:
function onSortEnd({ oldIndex, newIndex, collection }) {
const newCollections = [...studentCourses];
newCollections[collection].courseList = arrayMove(
newCollections[collection].courseList,
oldIndex,
newIndex
);
updateCoursesOrder(newCollections);
}
The newCollection gets populated and modified correctly and I am calling updateCoursesOrder with the items correctly arranged. That function is an action that calls a dispatch.
export function updateCoursesOrder(courseList) {
return function (dispatch) {
dispatch(setNewCourseOrderSuccess(courseList));
};
}
export function setNewCourseOrderSuccess(studentCourses) {
return { type: types.SET_NEW_COURSE_ORDER, studentCourses };
}
Using the debugger I can see that the code is running well up till the dispatch return from setNewCourseOrderSuccess().
This should go to the reducer, but instead throws an error: Uncaught Invariant Violation: A state mutation was detected between dispatches.
This is how the reducer looks like:
export default function courseReducer(
state = initialState.studentCourses,
action
) {
switch (action.type) {
case type.LOAD_STUDENT_COURSES_SUCCESS:
return action.studentCourses;
case type.SET_NEW_COURSE_ORDER:
return {
...state,
studentCourses: action.payload,
};
default:
return state;
}
}
How could I solve this issue?
Thank you very much!
With this:
const newCollections = [...studentCourses];
newCollections[collection].courseList =
Although newCollections is a new array, it's only a shallow copy of the studentCourses; the array items are not clones, they're references to current objects in state. So, assigning to a courseList property of one of those objects is mutating the state.
Replace the object at the index instead of mutating it:
function onSortEnd({ oldIndex, newIndex, collection }) {
updateCoursesOrder(studentCourses.map((item, i) => i !== collection ? item : ({
...item,
courseList: arrayMove(item.courseList, oldIndex, newIndex)
})));
}
I have code like this for my component which is a function component:
useEffect(() => {
if (errors['update'].error || successes['update'].success) {
setUpdateInProgress(false);
}
}, [errors['update'].error, successes['update'].success])
error and success properties are independent of one another, and when either of them is true, I want to call setUpdateInProgress.
in mapStateToProps I have code like this:
const mapStateToProps = (store) => {
return {
// other fields
errors: {...store.prop1.errors, ...store.prop2.errors},
successes: {...store.prop1.errors, ...store.prop2.errors};
}
These errors and successes objects are the props I need to send to my component in order for the useEffect to work. As is apparent, I want to combine properties from multiple places into one(prop1,prop2, ...).
Problem is, it's not working, and the useEffect is not called. To get it to work, I have to split the props like below:
const mapStateToProps = (store) => {
return {
// other fields
prop1Errors: store.prop1.errors,
prop2Errors: store.prop2.errors,
prop1Successes: store.prop1.successes,
prop2Successes: store.prop2.successes
}
Why would the spread operator lead to a stale value being set from the state whereas using the state value directly wouldn't?
Edit: For those interested, errors and successes have a structure like this:
errors: {
create: {
error: true,
message: "Error in create"
},
update: {
error: false,
message: ""
}
//... same thing for other actions like fetch, etc
}
//... same thing for successes
As for the reducer code, it's something like this:
function reducer(state, action) {
if(action === "UPDATE") {
return {...state, errors: { ...state.errors, update: { message: '', error: false }}, successes: { ...state.successes, update: { message: 'Update successful.', success: true }}}
}
}
Spread properties in object initializers copy their own enumerable properties from a provided object onto the newly created object. If the spread property already exists in the newly created object, it will be automatically replaced.
In other words, if you have 2 objects: const foo = {a: 1} and const bar = {a: 2, b: 1}. The result of {...foo, ...bar} would be {a: 2, b: 1}. So you can see that the assigned a property was the one from the last spread object.
This is exactly your case. You must consider deep merging since you have a multi-dimensional structure for errors and successes.
Based on your errors structure, I would do a function that merges the errors/successes as follows:
const mergeMessages = (type, ...messages) =>
{
return messages.reduce((carry, message) => {
for (let [key, value] of Object.entries(message)) {
if (!carry.hasOwnProperty(key) || value[type] === true) {
carry[key] = value;
}
}
return carry;
}, {});
}
Then you can use it like:
const mapStateToProps = (store) => {
return {
// other fields
errors: mergeMessages('error', store.prop1.errors, store.prop2.errors),
successes: mergeMessages('success', store.prop1.successes, store.prop2.successes),
}
}
Inside my reducer, my initial state has a structure like the following:
const initialState = {
showOverlay: false,
chosenAnimal: null,
sliderValue: 0,
colorValues: {
a: null,
c: null,
g: null,
t: null,
bg: null
}
};
I'm attempting to update one of the colorValues properties based on an action.type.
return {
...state,
colorValues: {...state.colorValues, action.basePair: action.colorHex}
};
action.basePair is giving me a parsing error which makes it seem as if I cannot set the property name dynamically using action. If I change action.basePair to gfor example, then the expected behavior occurs and the value of the property g is indeed updated as expected.
But is there anyway to do this so that I can set the property name dynamically through action? Thank you.
edit: The code of my action is the following:
const mapDispatchToProps = dispatch => {
return {
onSetColorValue: (basePair, colorHex) =>
dispatch({ type: "SET_COLOR_VALUES", basePair: basePair, colorHex: colorHex })
};
};
action.basePair is giving me a parsing error which makes it seem as if
I cannot set the property name dynamically using action.
Wrap action.basePair to [action.basePair]
return {
...state,
colorValues: {...state.colorValues, [action.basePair]: action.colorHex}
};
Also, it is a good practice to use payload key for action params
dispatch({ type: "SET_COLOR_VALUES", payload: { basePair, colorHex })
Reducer
return {
...state,
colorValues: {
...state.colorValues, [action.payload.basePair]: action.payload.colorHex
}
};
when using dynamic value as key, we can use the square brackets notation. Please see below:
return {
...state,
colorValues: {
...state.colorValues,
[action.basePair]: action.colorHex
}
};
You can visit below link for more details:
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Property_accessors
I have a React Redux app which gets data from my server and displays that data.
I am displaying the data in my parent container with something like:
render(){
var dataList = this.props.data.map( (data)=> <CustomComponent key={data.id}> data.name </CustomComponent>)
return (
<div>
{dataList}
</div>
)
}
When I interact with my app, sometimes, I need to update a specific CustomComponent.
Since each CustomComponent has an id I send that to my server with some data about what the user chose. (ie it's a form)
The server responds with the updated object for that id.
And in my redux module, I iterate through my current data state and find the object whose id's
export function receiveNewData(id){
return (dispatch, getState) => {
const currentData = getState().data
for (var i=0; i < currentData.length; i++){
if (currentData[i] === id) {
const updatedDataObject = Object.assign({},currentData[i], {newParam:"blahBlah"})
allUpdatedData = [
...currentData.slice(0,i),
updatedDataObject,
...currentData.slice(i+1)
]
dispatch(updateData(allUpdatedData))
break
}
}
}
}
const updateData = createAction("UPDATE_DATA")
createAction comes from redux-actions which basically creates an object of {type, payload}. (It standardizes action creators)
Anyways, from this example you can see that each time I have a change I constantly iterate through my entire array to identify which object is changing.
This seems inefficient to me considering I already have the id of that object.
I'm wondering if there is a better way to handle this for React / Redux? Any suggestions?
Your action creator is doing too much. It's taking on work that belongs in the reducer. All your action creator need do is announce what to change, not how to change it. e.g.
export function updateData(id, data) {
return {
type: 'UPDATE_DATA',
id: id,
data: data
};
}
Now move all that logic into the reducer. e.g.
case 'UPDATE_DATA':
const index = state.items.findIndex((item) => item.id === action.id);
return Object.assign({}, state, {
items: [
...state.items.slice(0, index),
Object.assign({}, state.items[index], action.data),
...state.items.slice(index + 1)
]
});
If you're worried about the O(n) call of Array#findIndex, then consider re-indexing your data with normalizr (or something similar). However only do this if you're experiencing performance problems; it shouldn't be necessary with small data sets.
Why not using an object indexed by id? You'll then only have to access the property of your object using it.
const data = { 1: { id: 1, name: 'one' }, 2: { id: 2, name: 'two' } }
Then your render will look like this:
render () {
return (
<div>
{Object.keys(this.props.data).forEach(key => {
const data = this.props.data[key]
return <CustomComponent key={data.id}>{data.name}</CustomComponent>
})}
</div>
)
}
And your receive data action, I updated a bit:
export function receiveNewData (id) {
return (dispatch, getState) => {
const currentData = getState().data
dispatch(updateData({
...currentData,
[id]: {
...currentData[id],
{ newParam: 'blahBlah' }
}
}))
}
}
Though I agree with David that a lot of the action logic should be moved to your reducer handler.