I am trying to figure out redux but I am stuck. I have an input that is in the header section and within the body section, I have a component that receives the data from the parent and renders them as list items. So far so good.
I have wired my input search text to fire a redux action on each key press.
my actions file
export const jobsFilterDisplay = (jobsList, filterValue) => async dispatch => {
if (!filterValue){
console.log("you should call fetch all again");
console.log(filterValue);
jobsFetchAll();
} else {
console.log("original list");
console.log(jobsList); // list of jobs - original and unfiltered
console.log(typeof jobsList); // returns object
console.log("filter value"); //
console.log(filterValue); // my search text input value
//
// I think this is where I am supposed to filter the original list
// and dispatch an event with the filtered object as the payload
// in my reducer, I will assign the payload to my list
// my list component should update automatically and render updated list
// dispatch({ type: JOBS_LIST_FILTERED, payload: filterValue });
}
}
I want to be able to search by customer name or any attribute. Whenever I tried _.filter or anything like that, I always ended up either not a function or undefined and I've been trying for more a long time.
my json example - part of it
{"jobs":[{"id":1,"ticket":{"number":121,"quote":12321,"tech":"Tech 1","date":"06-22-2017","status":1},"customer":{"name":"John","address":"USA"}},{"id":2,"ticket":{"number":1231231,"quote":21123,"tech":"Tech 4","date":"06-22-2017","status":2},"customer":{"name":"Doe","address":"CANADA"}}]}
Any help is appreciated . Thank you!
You can use Array.prototype.filter() for this. Are you using lodash currently? That is also possible. But I recommend Array.prototype.filter() approach. Here's how you can do it.
const payload =
[{"id":1,"ticket":{"number":121,"quote":12321,"tech":"Tech 1","date":"06-22-2017","status":1}, "customer":{"name":"John","address":"USA"}},
{"id":2,"ticket":{"number":1231231,"quote":21123,"tech":"Tech 4","date":"06-22-2017","status":2},"customer":{"name":"Doe","address":"CANADA"}}]
function filterByCustomerName(customerName, payload) {
return payload.filter(job => job.customer.name === customerName);
}
const job = filterByCustomerName('Doe', payload);
console.log(job);
Anyway If you really need to use lodash, make sure you import it like this. Then use the filter from the lodash library. But still I don't see any fair reason for you to go for it.
import _ from 'lodash';
function fetJobByCustomer(customerName, payload) {
return _.filter(payload, job => job.customer.name === customerName);
});
}
Hope this helps. Happy Coding !
Related
I am currently following a React course on Scrimba on creating a web app for taking notes.
The problem requires me to bump a note to the top of the note list every time it's updated.
The notes are initialised through useState as follows:
const [notes, setNotes] = useState([])
The array consists of the individual notes as objects with an id and body
Every time an onChange is triggered, the following function is ran:
function updateNote(text) {
setNotes(oldNotes => {
let updated = oldNotes.map(oldNote => {
return oldNote.id === currentNoteId
? { ...oldNote, body: text }
: oldNote
})
const currNoteIndex = updated.findIndex(
note => note.id === currentNoteId
)
console.log(currNoteIndex)
updated.unshift(updated.splice(currNoteIndex, 1))
return updated
})
}
However, I keep getting an error as shown in the image.
It's very unclear to me where the problem lies, but I'm thinking it has to do with the array methods.
Any explanation for this issue would be greatly appreciated!
Credits to jsN00b for the answer:
array.splice returns an array, not the object.
Since that array is inserted to the start of the array containing the objects, there will be an error when updateNote() is called again.
I have this form with multiple checkboxes and below it, I also have the others where the user can enter any value. The problem is that if I'll enter a value for the 2nd time, it will remove the previous value entered by the user.
Assuming that I've entered books for my first submit. Now, I want to submit another value for the others again, but this time it will be movies. I want to save in the firestore the both of these values; books and movies. The problem is that if I'll submit movies, this will override the previous one books, meaning it will replace books. How can I avoid that and at the same time display the multiple values entered by the user in the field others?
Below are the codes:
const sample = (props) => {
const [others, setOthers] = useState("");
const handleSubmit = (e) => {
e.preventDefault();
try {
const user = firestore.collection("users").doc(id);
const ref = user.set(
{
1: {
choices,
others
}
},
{ merge: true }
);
console.log(" saved");
} catch (error) {
console.log(error);
}
};
return (
<>
<form onSubmit={handleSubmit}>
<FormGroup>
//codes for the checkboxes here
<TextField
type="text"
label="Others:"
value={others}
onChange={(e) => setOthers(e.target.value)}
multiline
/>
</FormGroup>
<button type="submit">Submit</button>
<br />
</form>
</>
);
};
export default sample;
im going to preface this with im not familiar with react, but i do mess around with firestore alot. so the syntax maybe different for you.
but the first thing i notice is that you're using const ref = user.set to make the document. this is fine for first time creating a document, but if you use '.set' on an existing document it will override all the data in that document with whatever you're attempting to update it with.
you should use const ref = user.update to update fields in the document.
the 2nd bit is lets say you want to update the 'others' field. it would still override the data in that field even if you use '.update'. update is doing just that, its updating the field in question with whatever you're trying to update it with. what you want to do is add to it.
so your 'others' field needs to be an array and in order to add new values into it without overriding the previous data you need to use an arrayUnion.
const handleSubmit = (e) => {
e.preventDefault();
try {
const user = firestore.collection("users").doc(id);
const ref = user.update(
{
1: {
choices,
others: firebase.firestore.FieldValue.arrayUnion(others),
}
},
{ merge: true }
);
console.log(" saved");
} catch (error) {
console.log(error);
}
};
now i dont know how imports work in react but in VUEjs you'd need to import import firebase from "firebase/compat/app"; in the script tag in order to use the that firebase feature.
if you want to remove an item from that others array then use.
const handleSubmit = (e) => {
e.preventDefault();
try {
const user = firestore.collection("users").doc(id);
const ref = user.update(
{
1: {
choices,
others: firebase.firestore.FieldValue.arrayRemove(item), //item = whatever it is you're trying to remove.
}
},
{ merge: true }
);
console.log(" saved");
} catch (error) {
console.log(error);
}
};
From the React docs,
this.props and this.state may be updated asynchronously, you should not rely on their values for calculating the next state. To fix it, use a second form of setState() that accepts a function rather than an object. That function will receive the previous state as the first argument, and the props at the time the update is applied as the second argument.
prevState is a name to the argument passed to setState callback function. What it holds is the value of state before the setState was triggered by React.
So if multiple setState calls are updating the same state, batching setState calls may lead to incorrect state being set. Consider an example.
Your 'others' field needs to be an array and in order to add new values into it without overriding the previous data you need to use prevState.
If you don’t want to use setState you can use
prevState in useState React Hook with Javascript spread operator to
concatenate the previous values with current values in array
Something like this.
This is a difficult one to explain so I will do my best!
My Goal
I have been learning React and decided to try build a Todo List App from scratch. I wanted to implement a "push notification" system, which when you say mark a todo as complete it will pop up in the bottom left corner saying for example "walk the dog has been updated". Then after a few seconds or so it will be removed from the UI.
Fairly simple Goal, and for the most part I have got it working... BUT... if you quickly mark a few todos as complete they will get removed from the UI and then get re-rendered back in!
I have tried as many different ways of removing items from state as I can think of and even changing where the component is pulled in etc.
This is probably a noobie question, but I am still learning!
Here is a link to a code sandbox, best way I could think of to show where I am at:
Alert Component State/Parent
https://codesandbox.io/s/runtime-night-h4czf?file=/src/components/layout/PageContainer.js
Alert Component
https://codesandbox.io/s/runtime-night-h4czf?file=/src/components/parts/Alert.js
Any help much appreciated!
When you call a set function to update state, it will update from the last rendered value. If you want it to update from the last set value, you need to pass the update function instead of just the new values.
For instance, you can change your setTodos in your markComplete function to something like this.
setTodos(todos => todos.map((todo) => {
if (id === todo.id) {
todo = {
...todo,
complete: !todo.complete,
};
}
return todo;
}));
https://codesandbox.io/s/jovial-yalow-yd0jz
If asynchronous events are happening, the value in the scope of the executed event handler might be out of date.
When updating lists of values, use the updating method which receives the previous state, for example
setAlerts(previousAlerts => {
const newAlerts = (build new alerts from prev alerts);
return newAlerts;
});
instead of directly using the alerts you got from useState.
In the PageContainer.js, modify this function
const removeAlert = (id) => {
setAlerts(alerts.filter((alert) => alert.id !== id));
};
to this
const removeAlert = (id) => {
setAlerts(prev => prev.filter((alert) => alert.id !== id));
};
This will also fix the issue when unchecking completed todos at high speed
I have an array that shows a set of items attached to a user. The array checks it first if the current user is on the item's inventory and then displayed as 'user name' and 'date borrowed' on a table. The adding feature is done in a modal, and suppose to update the table.
The problem is everytime I add, delete or update, the table doesn't update at all. Also this table is an expandend component of another table (react-data-table-component)
Here is the useState, and useEffect of my table:
const InventoryTable= ({
selectedUser,
items,
getItems,
getUsers
}) => {
useEffect(() => {
getItems();
getUsers();
}, []);
const [data, setData] = useState([]);
useEffect(() => {
let data= [];
data= items?.filter((item) =>
item?.users.some(
(user) => parseInt(user?.id) === parseInt(selectedUser?._id)
)
);
setData(data);
}, []);
Note: selectedUser, is the user from the main table that was selected and this current table is to show the itms attached to it.
If I add data on the setData(data); }, []); it crashes.
Adding data, selectedUser, and items on the dependency arrays loads it non-stop that causes to crash the page
useEffect method takes 2 parameters as input.
callback
dependency
If the dependency is empty array it will be called in the similar way as Class Component with componentDidMount and componentWillUnmount lifecycle methods.
if dependency is present then the callback will be called after the UI in painted.
So clearly you have missed required dependency
I'm not sure that i understand the whole concepts of your code because it is just a small part of the code. But useEffect() will run accordingly if you want it to run as ComponenentDidMount you will use the code that you said above however in your case you want to update delete add it means you want to detect the change in data so you need to include data within the brackets like this
`useEffect(() => {
let data= [];
data= items?.filter((item) =>
item?.users.some(
(user) => parseInt(user?.id) === parseInt(selectedUser?._id)
)
);
setData(data);
}, [data,items,selectedUser]);`
Seems you forgot to pass items and selectedUser to effect dependency array.
Maybe I am wrong, but have you tried renaming data property inside useEffect? It should have problem, that you are setting state without set callback.
Try also set useState default value to [] instead of {} (object).
I am using Firebase and Node with Redux. I am loading all objects from a key as follows.
firebaseDb.child('invites').on('child_added', snapshot => {
})
The idea behind this method is that we get a payload from the database and only use one action to updated my local data stores via the Reducers.
Next, I need to listen for any NEW or UPDATED children of the key invite.
The problem now, however, is that the child_added event triggers for all existing keys, as well as newly added ones. I do not want this behaviour, I only require new keys, as I have the existing data retrieved.
I am aware that child_added is typically used for this type of operation, however, i wish to reduce the number of actions fired, and renders triggered as a result.
What would be the best pattern to achieve this goal?
Thanks,
Although the limit method is pretty good and efficient, but you still need to add a check to the child_added for the last item that will be grabbed. Also I don't know if it's still the case, but you might get "old" events from previously deleted items, so you might need to watch at for this too.
Other solutions would be to either:
Use a boolean that will prevent old added objects to call the callback
let newItems = false
firebaseDb.child('invites').on('child_added', snapshot => {
if (!newItems) { return }
// do
})
firebaseDb.child('invites').once('value', () => {
newItems = true
})
The disadvantage of this method is that it would imply getting events that will do nothing but still if you have a big initial list might be problematic.
Or if you have a timestamp on your invites, do something like
firebaseDb.child('invites')
.orderByChild('timestamp')
.startAt(Date.now())
.on('child_added', snapshot => {
// do
})
I have solved the problem using the following method.
firebaseDb.child('invites').limitToLast(1).on('child_added', cb)
firebaseDb.child('invites').on('child_changed', cb)
limitToLast(1) gets the last child object of invites, and then listens for any new ones, passing a snapshot object to the cb callback.
child_changed listens for any child update to invites, passing a snapshot to the cb
I solved this by ignoring child_added all together, and using just child_changed. The way I did this was to perform an update() on any items i needed to handle after pushing them to the database. This solution will depend on your needs, but one example is to update a timestamp key whenever you want the event triggered. For example:
var newObj = { ... }
// push the new item with no events
fb.push(newObj)
// update a timestamp key on the item to trigger child_changed
fb.update({ updated: yourTimeStamp })
there was also another solution:
get the number of children and extract that value:
and it's working.
var ref = firebaseDb.child('invites')
ref.once('value').then((dataSnapshot) => {
return dataSnapshot.numChildren()
}).then((count) =>{
ref .on('child_added', (child) => {
if(count>0){
count--
return
}
console.log("child really added")
});
});
If your document keys are time based (unix epoch, ISO8601 or the firebase 'push' keys), this approach, similar to the second approach #balthazar proposed, worked well for us:
const maxDataPoints = 100;
const ref = firebase.database().ref("someKey").orderByKey();
// load the initial data, up to whatever max rows we want
const initialData = await ref.limitToLast(maxDataPoints).once("value")
// get the last key of the data we retrieved
const lastDataPoint = initialDataTimebasedKeys.length > 0 ? initialDataTimebasedKeys[initialDataTimebasedKeys.length - 1].toString() : "0"
// start listening for additions past this point...
// this works because we're fetching ordered by key
// and the key is timebased
const subscriptionRef = ref.startAt(lastDataPoint + "0");
const listener = subscriptionRef.on("child_added", async (snapshot) => {
// do something here
});