Combine two objects in react, no success so far - javascript

I'm trying to combine two object together without any luck...
I'm using the spread operator as you should in es6. I'm pretty sure it has something to do with useState. I will start by showing you the console.logs from my objects then the code :)
CONSOLE.LOGS
Object #1:
Object #2:
After combining the objects and logging the combined result I only get Object #1 like this:
MY CODE
const PortfolioComponent = () => {
const [clickedElement, setClickedElement] = useState({})
const [stockObject, setStockObject] = useState({})
const [stockData, setStockData] = useState({})
const getStock = () => {
axios.request(stock)
.then( res => {
console.log("2. Object #2 from API: ", res.data)
setStockData(res.data) // HERE I SET THE OBJECT FROM MY API CALL
})
.catch( error => {
console.error(error);
}
const handleOpen = name => { // In this function i call the api function and combine the two objects.
let findClickedStock = props.stocksArray.find(item => item.ticker === name)
setClickedElement(findClickedStock)
getStock();
console.log("1. Object #1: ", findClickedStock)
setTimeout(() => { // Waitning for the api to fetch
setStockObject({...findClickedStock, ...stockData}) // Here i combine the two objects, no success
console.log("3. Combined object - Only gets Object #1...", stockObject)
}, 2000);
setOpen(true);
};
}
export default PortfolioComponent;

From the Hooks API Reference:
Note
Unlike the setState method found in class components, useState does not automatically merge update objects. You can replicate this behavior by combining the function updater form with object spread syntax:
setState(prevState => {
// Object.assign would also work
return {...prevState, ...updatedValues};
});
Also, check out useReducer which is generally recommended for managing state with multiple sub-values.

const getStock = () => {
return axios.request(stock)
.then( res => res.data)
.catch( error => {
console.error(error);
}
const handleOpen = name => { // In this function i call the api function and combine the two objects.
let findClickedStock = props.stocksArray.find(item => item.ticker === name)
setClickedElement(findClickedStock)
console.log("1. Object #1: ", findClickedStock)
getStock().then((dataFromStockApi) => {
setStockObject({ ...dataFromStockApi, ...findClickedStock });
});
setOpen(true);
};

Related

React array state is being returned as object

I am using a MUI Autocomplete field that takes an array for options.
I created this hook that takes the input value and fetches the API based on it.
This is the code for it:
import axios from "axios";
import { useEffect, useState } from "react";
export default function useFetchGames(searchString) {
const [gameSearch, setGameSearch] = useState([]);
useEffect(() => {
if (searchString) setGameSearch(fetchData(searchString));
}, [searchString]);
return gameSearch;
}
const generateSearchOptions = (array) => {
const tempArray = [];
array.map((item) => {
tempArray.push(item.name);
});
return tempArray;
};
async function fetchData(searchString) {
const res = await axios
.post("/api/games", { input: searchString })
.catch((err) => {
console.log(err);
});
return generateSearchOptions(res.data);
}
And then i am calling this hook in the component where i have the autocomplete element.
const searchOptions = useFetchGames(inputValue);
The issue is,useFetchGames is supposed to return an array since the state is an array. But whenever the input changes, i get an error that you cant filter or map an object. Basically Autocompolete element is trying to map searchOptions but it is not an array.
I even tried to log its type with log(typeof searchOptions); and it returns an object.
I dont understand what I am doing wrong.
Edit: Here is the log of res.data. it is an array of objects. That is why i am remapping it to an array of just the names.
you get the promise back when you invoked fetchData(searchString) as it is an async function which always return a promise back
I would do it as
useEffect(() => {
// wrapping it as a async function so we can await the data (promise) returned from another async function
async function getData() {
const data = await fetchData(searchString);
setGameSearch(data);
}
if (searchString) {
getData();
}
}, [searchString]);
also refactoring this generateSearchOptions function in order to remove the creation of temp array which I feel is not required when using a map - below as
const generateSearchOptions = (array) => array.map(item => item.name)

converting class to hooks getting Property 'then' does not exist on type '(dispatch: any) => Promise<void>'.ts(2339)

I'm new to react, here I have two same codes, one is with classes that work, and another is converted from that same class into hooks.
in hooks version, my 'then' is giving an error
Property 'then' does not exist on type '(dispatch: any) =>
Promise'.ts(2339)
have I made some mistake with conversion?
why it is not giving the same error in class while both are the same?
also console.log("Fetched model", realGraph.model); should give an object but it is giving undefined(in-class version it works), but if I put this console outside of loadGraph function then it gives an object, why it's not giving an object inside loadGraph function?
any ideas and suggestions?
class:
import { getGraph, getFloorplan, changeActiveCamera } from '../redux/actions';
const mapStateToProps = (state) => {
return {
currentSite: state.selection.currentSite,
currentCamera: state.selection.currentCamera,
};
};
function mapDispatchToProps(dispatch) {
return {
getGraph: (site) => dispatch(getGraph(site)),
getFloorplan: (site) => dispatch(getFloorplan(site)),
changeActiveCamera: (site, id) => dispatch(changeActiveCamera(site, id)),
};
}
loadGraph() {
if (this.props.currentSite) {
this.props.getFloorplan(this.props.currentSite.identif).then(() => {
console.log('Fetched floorplan');
this.props.getGraph(this.props.currentSite.identif).then(() => {
console.log('Fetched model', this.props.realGraph.model);
// new camera-related node & link status
if (this.props.currentCamera) {
this.props.changeActiveCamera(
this.props.currentSite.identif,
this.props.currentCamera.identif
);
}
});
});
}
}
converted from class to hooks:
Hooks:
const dispatch = useDispatch();
const realGraph = useSelector((state) => state.graphArticles.graph);
const currentSite = useSelector((state) => state.selection.currentSite);
const currentCamera = useSelector((state) => state.selection.currentCamera);
const dispatchGetFloorplan = (site) => dispatch(getFloorplan(site));
const dispatchGetGraph = (site) => dispatch(getGraph(site));
const dispatchChangeActiveCamera = (site, id) =>
dispatch(changeActiveCamera(site, id));
const loadGraph = () => {
if (currentSite) {
dispatchGetFloorplan(currentSite.identif).then(() => {
console.log('Fetched floorplan');
dispatchGetGraph(currentSite.identif).then(() => {
console.log('Fetched model', realGraph.model);
// new camera-related node & link status
if (currentCamera) {
dispatchChangeActiveCamera(
currentSite.identif,
currentCamera.identif
);
}
});
});
}
};
my action related to those:
export function getGraph(site) {
return getData(`api/graph/${site}`, GET_GRAPHS);
}
export function getFloorplan(site) {
return getImage(`api/graph/${site}/floorplan`, GET_FLOORPLAN);
}
On first glance, there are several things I would change in the code you provided.
First, don't use any wrapper factories over your dispatch functions. Use dispatch(action()) directly where you need it component. You aren't gaining anything by creating wrapper functions.
Second, it would be advisable to use some sort of middleware, like Redux Thunk, to handle async Redux actions (like fetching something from the API).
The actions you provided are just "dumb" functions, which are not returning promises so you can't expect it to be "then"-able in your target component.
I also advise the async/await syntax since it is much more readable.
Third, you need to leverage the Hooks reactive API with the useEffect hook.
So first try to define getFloorPlan and getGraph as async actions using the redux-thunk syntax.
export const getGraphAsync = (site) => async (dispatch) => {
try {
const data = await getData(`api/graph/${site}`, GET_GRAPHS);
dispatch(saveGraphData(data)) // save data into Redux store with a normal, synchronous action (plain object)
} catch (error) {
console.log(error)
}
}
export const getFloorplanAsync = (site) => async (dispatch) => {
try {
const data = await getImage(`api/graph/${site}/floorplan`, GET_FLOORPLAN);
dispatch(saveImageData(data)) // save data into Redux store with a normal, synchronous action (plain object)
} catch (error) {
console.log(error)
}
}
I am making an assumption that you correctly configured your store.js to use the thunk middleware.
And then refactor the rest of the component (following some best practices):
const someHookComponent = () => {
// ...
const dispatch = useDispatch();
const currentSite = useSelector((state) =>
state.selection.currentSite);
const currentCamera = useSelector((state) =>
state.selection.currentCamera);
const loadGraph = async () => {
if (currentSite) {
await dispatch(getFloorPlanAsync(currentSite.identif));
console.log('Fetched floorplan');
await dispatch(getGraphAsync(currentSite.identif));
console.log('Fetched model', realGraph.model); /* where is
realGraph coming from? */
/* Why is it important that these 2 dispatches follow one
another when there is no data being passed from one to the
other, or being used later in the component... */
});
});
}
};
useEffect(() => {
// new camera-related node & link status
if (currentCamera) {
dispatch(changeActiveCamera(
currentSite.identif,
currentCamera.identif
));
}
}, [currentSite?.identif, currentCamera?.identif]) /* null chaining is optional here */
// ...
}
I am guessing that loadGraph gets called by some onClick event somewhere down the line like this:
onClick={loadGraph}
If it is called inside useEffect, define the deps (variables used inside loadGraph):
useDeepCompareEffect(() => {
// ... some logic
loadGraph()
}, [currentSite, realGraph])
If you put your currentSite and currentCamera objects directly into the useEffect list of deps then you need to do a deep comparison "by hand".
In that case it's best to create a custom hook like useDeepCompareEffect which will do the heavy lifting of running deep comparisons of reference types under the hood (with the help of some library like lodash for example).
If you want to use or console.log the latest value of realGraph (reference type), you need to use the useEffect hook with a deep comparison again (or just extract the target primitive directly into the deps list and use vanilla useEffect) :
useDeepCompareEffect(() => {
if (realGraph) {
console.log('Fetched model', realGraph.model);
}
}, [realGraph]) // reference type
// or
useEffect(() => {
if (realGraph) {
console.log('Fetched model', realGraph.model);
}
}, [realGraph.someProperty]) // primitive

Object deconstructing in javascript [duplicate]

This question already has answers here:
The useState set method is not reflecting a change immediately
(15 answers)
Closed 1 year ago.
This post was edited and submitted for review 1 year ago and failed to reopen the post:
Original close reason(s) were not resolved
I've been trying to deconstruct an object. The object comes via json as a result of single column select from the database.
//api
export const fetchNames = async() => {
try {
const data = await axios.get("http://localhost:5000/names");
return data;
} catch (error) {
return error
}
}
//function call
const [fetchedNames, setfetchedNames] = useState([]);
useEffect(()=>{
const fetchApi = async () => {
fetchedNames(await fetchNames());
}
fetchApi();
console.log(fetchedNames);
})
result:
data:Array(23)
0:{name:"adams"}
Expected is an array of all names. [ADAMS, SIMON, ...].
The array will be use in a NativeSelect and will be display as frontend selection.
Approach i did that resulted to my expected output.
export const fetchNames = async () =>{
try{
const response = await fetch(`http://localhost:5000/towns`);
const jsonNames = await response.json();
return jsonNames;
}catch(error){
return error;
}
}
const [fetchedNames, setFetchedNames] = useState([]);
useEffect(()=>{
const fetchApi = async () =>{
setFetchedNames( await fetchNames());
}
fetchApi();
},[]);
Then, i did the mapping.
{fetchedNames.map((Names,i) => (<option key={i} value
{Names.name}>{Names.name}))}
There are a few issues there:
fetchedNames(await fetchNames()); is trying to call an array, not a function; the setter is setFetchedNames, not fetchedNames.
You're converting rejection to fulfillment with an error.
You don't have a dependency array, so the effect gets called after every render.
There's no proper error handling when using the API function.
Doing console.log(fetchedNames) immediately after calling its setter will still show you the old value; your component sees the new value when React calls it again later to re-render because of the state change.
I think you're probably looking for something like this, assuming you only want to fetch the data once when the component mounts, see *** comments:
//api
export const fetchNames = async () => {
// *** Removed the `try`/`catch`, you shouldn't handle that here, let the caller handle it
const data = await axios.get("http://localhost:5000/names");
return data; // *** Is this really correct? Not `data.data`?
}; // *** I assumed a closing } here
// In your component function
const [fetchedNames, setfetchedNames] = useState([]);
useEffect(() => {
// An async function wrapper doesn't help anything here
fetchNames()
.then(setFetchedNames) // *** Call the setter function, not the array
.catch(error => {
// ...handle/report error...
});
}, []); // *** You need a dependencies array, or your effect is called after every render
// ...use `fetchedNames` to render the component; it will initially be
// empty, then your component will be re-rendered (with the names in
// `fetchedNames`) when you get the names from the API.
Aside from the above, if you only want the names but the array you get is of objects with a name property, add a map call, probably in the API function:
export const fetchNames = async () => {
const data = await axios.get("http://localhost:5000/names");
return data.map(({name}) => name);
// ^^^^^^^^^^^^^^^^^^^^^^
};
As you mentioned, if your API response shape is like data array, you can simply use map function to get all the values to an array.
const data = [{name: 'A'}, {name: 'B'}, {name: 'C'}, {name: 'D'}];
const dataArray = data.map(entry => entry.name);
console.log(dataArray); // ["A","B","C","D"]
export const fetchNames = async() =>{
try {
const data = await axios.get("http://localhost:5000/names");
return data;
} catch (error) {
return error
}
//function call[enter image description here][1]
const [fetchedNames, setfetchedNames ]= useState([]);
useEffect(()=>{
fetchNames().then((data) => {
setfetchedNames(p => [...p, data.names])
})
console.log(fetchedNames);
})
you can try to type this code instead it uses the returned value from fetchNames then stores each name in the state fetchedNames.

setState in nested async function - React Hooks

How can I build a function which gets some data asynchronously then uses that data to get more asynchronous data?
I am using Dexie.js (indexedDB wrapper) to store data about a direct message. One thing I store in the object is the user id which I'm going to be sending messages to. To build a better UI I'm also getting some information about that user such as the profile picture, username, and display name which is stored on a remote rdbms. To build a complete link component in need data from both databases (local indexedDB and remote rdbms).
My solution returns an empty array. It is being computed when logging it in Google Chrome and I do see my data. However because this is not being computed at render time the array is always empty and therefor I can't iterate over it to build a component.
const [conversations, setConversations] = useState<IConversation[]>()
const [receivers, setReceivers] = useState<Profile[]>()
useEffect(() => {
messagesDatabase.conversations.toArray().then(result => {
setConversations(result)
})
}, [])
useEffect(() => {
if (conversations) {
const getReceivers = async () => {
let receivers: Profile[] = []
await conversations.forEach(async (element) => {
const receiver = await getProfileById(element.conversationWith, token)
// the above await is a javascript fetch call to my backend that returns json about the user values I mentioned
receivers.push(receiver)
})
return receivers
}
getReceivers().then(receivers => {
setReceivers(receivers)
})
}
}, [conversations])
/*
The below log logs an array with a length of 0; receivers.length -> 0
but when clicking the log in Chrome I see:
[
0: {
avatarURL: "https://lh3.googleusercontent.com/..."
displayName: "Cool guy"
userId: "1234"
username: "cool_guy"
}
1: ...
]
*/
console.log(receivers)
My plan is to then iterate over this array using map
{
receivers && conversations
? receivers.map((element, index) => {
return <ChatLink
path={conversations[index].path}
lastMessage={conversations[index].last_message}
displayName={element.displayName}
username={element.username}
avatarURL={element.avatarURL}
key={index}
/>
})
: null
}
How can I write this to not return a empty array?
Here's a SO question related to what I'm experiencing here
I believe your issue is related to you second useEffect hook when you attempt to do the following:
const getReceivers = async () => {
let receivers: Profile[] = []
await conversations.forEach(async (element) => {
const receiver = await getProfileById(element.conversationWith, token)
receivers.push(receiver)
})
return receivers
}
getReceivers().then(receivers => {
setReceivers(receivers)
})
}
Unfortunately, this won't work because async/await doesn't work with forEach. You either need to use for...of or Promise.all() to properly iterate through all conversations, call your API, and then set the state once it's all done.
Here's is a solution using Promise.all():
function App() {
const [conversations, setConversations] = useState<IConversation[]>([]);
const [receivers, setReceivers] = useState<Profile[]>([]);
useEffect(() => {
messagesDatabase.conversations.toArray().then(result => {
setConversations(result);
});
}, []);
useEffect(() => {
if (conversations.length === 0) {
return;
}
async function getReceivers() {
const receivers: Profile[] = await Promise.all(
conversations.map(conversation =>
getProfileById(element.conversationWith, token)
)
);
setReceivers(receivers);
}
getReceivers()
}, [conversations]);
// NOTE: You don't have to do the `receivers && conversations`
// check, and since both are arrays, you should check whether
// `receivers.length !== 0` and `conversations.length !== 0`
// if you want to render something conditionally, but since your
// initial `receivers` state is an empty array, you could just
// render that instead and you won't be seeing anything until
// that array is populated with some data after all fetching is
// done, however, for a better UX, you should probably indicate
// that things are loading and show something rather than returning
// an empty array or null
return receivers.map((receiver, idx) => <ChatLink />)
// or, alternatively
return receivers.length !== 0 ? (
receivers.map((receiver, idx) => <ChatLink />)
) : (
<p>Loading...</p>
);
}
Alternatively, using for...of, you could do the following:
function App() {
const [conversations, setConversations] = useState<IConversation[]>([]);
const [receivers, setReceivers] = useState<Profile[]>([]);
useEffect(() => {
messagesDatabase.conversations.toArray().then(result => {
setConversations(result);
});
}, []);
useEffect(() => {
if (conversations.length === 0) {
return;
}
async function getReceivers() {
let receivers: Profile[] = [];
const profiles = conversations.map(conversation =>
getProfileById(conversation.conversationWith, token)
);
for (const profile of profiles) {
const receiver = await profile;
receivers.push(receiver);
}
return receivers;
}
getReceivers().then(receivers => {
setReceivers(receivers);
});
}, [conversations]);
return receivers.map((receiver, idx) => <ChatLink />);
}
i think it is happening because for getReceivers() function is asynchronous. it waits for the response, in that meantime your state renders with empty array.
you can display spinner untill the response received.
like
const[isLoading,setLoading]= useState(true)
useEffect(()=>{
getReceivers().then(()=>{setLoading(false)}).catch(..)
} )
return {isLoading ? <spinner/> : <yourdata/>}
Please set receivers initial value as array
const [receivers, setReceivers] = useState<Profile[]>([])
Also foreach will not wait as you expect use for loop instead of foreach
I am not sure it is solution for your question
but it could help you to solve your error

Svelte derived stores and array sort

I set up a store containing a list of rides loaded from my API:
const loadRides = () => client.service('rides').find({
query: {
$sort: {
date: -1,
}
}
});
const createRides = () => {
const { subscribe, update } = writable([], async (set) => {
try {
const rides = await loadRides().then((result) => result.data);
set(rides);
} catch (e) {
console.error(e);
}
// Socket update binding?
});
subscribe((rides) => console.debug('rides', rides));
return {
subscribe,
refresh: () => loadRides().then((result) => update(() => result.data)),
};
};
export const rides = createRides();
Then I set a two derived stores for past and future rides:
export const pastRides = derived(
rides,
($rides) => $rides
.filter((ride) => ride.steps.every((step) => step.doneAt))
,
);
export const comingRides = derived(
rides,
($rides) => $rides
.filter((ride) => ride.steps.some((step) => !step.doneAt))
.sort((rideA, rideB) => {
const compare = new Date(rideA.date) - new Date(rideB.date);
console.log(rideA.date, rideB.date, compare);
return compare;
})
,
);
The sort method on the second one does not have any effect.
So I tried to put this method before the filter one. It works, but it also sort $pastRides. In fact, it is sorting the full $rides array and it make sens.
But I does not understand why the sort after filter does not work.
What did I miss?
Array.sort is mutable. Meaning, when you call rides.sort, it will sort and modify rides and return the sorted rides.
When you use derived(rides, ($rides) => ... ), the $rides you received is the original rides array that you call set(rides). So you can imagine that both the pastRides and comingRides received the same $rides array.
you can observe this behavior in this repl
To not having both derived interfere with each other, you can create a new array and sort the new array:
const sorted_1 = derived(array, $a => [...$a].sort());
you can try this out in this repl.

Categories