Trying to map an array of objects in react - reactjs

im doing a mern app, and i need to map over an array of objects, but the problem is that, the way i structure the data didn't put me in a good position to do it...
Here's the thing
this is my data
Inside task, there is an object, and inside that object we got more stuff...
I need to get to this data and show it in my website
So far, i only got to the creator
const tasks = useSelector(state => state.tasks.map(room => room.creator));
console.log(tasks);

The role of selectors is to select data from the state manager (redux in that context).
So firstly, fix your selector and select the tasks:
const tasks = useSelector(state => state.tasks);
Secondly,
Iterate over the tasks array and output the data you want:
const ExampleComponent = () => {
const tasks = useSelector(state => state.tasks);
return (
<ul>
{
tasks.map((task, index) => {
<li key={index}>{task.task}</li>
}
}
</ul>
);
}

Related

Why is the data displayed only through the map in react?

I only need to display the data of one object that I get here
const {posts, loading} = useSelector((state) => ({...state.posts}))
useEffect(() => {
dispatch(getPost(id))
}, []);
"posts" is array
when I want to display it simply this way
<h4>cat: {posts[0].category}</h4>
<h4>aut: {posts[0].username}</h4>
it only works only for the first time and after one page refresh the array (posts) turns out to be empty. But when I do it like this
<>
{
posts.map(post=>(
<CardDetailsComponent post={post} key={post._id}/>
))
}
</>
(CardDetails component looks exactly like first example but in different component)
everything is fine. Why?

Results from realtime db not available when called

Im having some trouble with realtime database and react native.
I have the following useEffect in a component that is supposed to listen for changes and then update state as required, I then use that state to populate a list.
The component gets a data object passed as a prop, the data object contains a string array called members that contains uuids, I am trying to iterate over those to get the attached user from realtime db and then save those objects to a state array.
const myComponent = ({ data }) => {
const [users, setUsers] = useState([]);
useEffect(() => {
const userArr = [];
data.map(item => {
item.members.forEach((username: string) => {
database()
.ref(`users/${username}`)
.on('value', snapshot => {
userArr.push(snapshot.val());
});
});
});
setUsers(userArr);
};
}, []);
return (
<>
{users} <----- this is in a flatlist
</>
);
}
It works eventually after refreshing the screen about 5 times. Any help would be greatly appreciated.
The simplest way to get some data to show is to move update the state right after you add an item to the array:
useEffect(() => {
const userArr = [];
data.map(item => {
item.members.forEach((username: string) => {
database()
.ref(`users/${username}`)
.on('value', snapshot => {
userArr.push(snapshot.val());
setUsers(userArr); // 👈
});
});
});
};
}, []);
Now the UI will update each time that a user is loaded from the database.
I also recommend reading some more about asynchronous loading, such as in Why Does Firebase Lose Reference outside the once() Function?
Your database call may be asynchronous, which is causing the code inside the useEffect to act a little funny. You could push all those database calls (while iterating through item.members) into an array, and then do Promise.all over the array. Once the promises are resolved, you can then set the users.
Hope this helps!
add an async function inside useEffect and call it
useEffect(() => {
const getUsers = async () => {
const userArr = [];
data.....
//wait for it with a promise
Promise.all(userArr).then(array => setUsers(array))
})
getUsers()
}, [])
not sure if the function needs to be async

Filter item on item list that is on state using useState and useEffect

I have a list of items on my application and I am trying to create a details page to each one on click. Moreover I am not managing how to do it with useState and useEffect with typescript, I could manage just using componentDidMount and is not my goal here.
On my state, called MetricState, I have "metrics" and I have seen in some places that I should add a "selectedMetric" to my state too. I think this is a weird solution since I just want to be able to call a dispatch with the action of "select metric with id x on the metrics list that is on state".
Here is the codesandbox, when you click on the "details" button you will see that is not printing the name of the selected metric coming from the state: https://codesandbox.io/s/react-listing-forked-6sd99
This is my State:
export type MetricState = {
metrics: IMetric[];
};
My actionCreators.js file that the dispatch calls
export function fetchMetric(catalog, metricId) {
const action = {
type: ACTION_TYPES.CHOOSE_METRIC,
payload: []
};
return fetchMetricCall(action, catalog, metricId);
}
const fetchMetricCall = (action, catalog, metricId) => async (dispatch) => {
dispatch({
type: action.type,
payload: { metrics: [catalog.metrics.filter((x) => x.name === metricId)] } //contains the data to be passed to reducer
});
};
and finally the MetricsDeatail.tsx page where I try to filter the selected item with the id coming from route parametes:
const metricId = props.match.params.metricId;
const catalog = useSelector<RootState, MetricState>(
(state) => state.fetchMetrics
);
const selectedMetric: IMetric = {
name: "",
owner: { team: "" }
};
const dispatch = useDispatch();
useEffect(() => {
dispatch(appReducer.actionCreators.fetchMetric(catalog, metricId));
}, []);
On my state, called MetricState, I have "metrics" and I have seen in some places that I should add a "selectedMetric" to my state too. I think this is a weird solution since I just want to be able to call a dispatch with the action of "select metric with id x on the metrics list that is on state".
I agree with you. The metricId comes from the URL through props so it does not also need to be in the state. You want to have a "single source of truth" for each piece of data.
In order to use this design pattern, your Redux store needs to be able to:
Fetch and store a single Metric based on its id.
Select a select Metric from the store by its id.
I generally find that to be easier with a keyed object state (Record<string, IMetric>), but an array works too.
Your component should look something like this:
const MetricDetailsPage: React.FC<MatchProps> = (props) => {
const metricId = props.match.params.metricId;
// just select the one metric that we want
// it might be undefined initially
const selectedMetric = useSelector<RootState, IMetric | undefined>(
(state) => state.fetchMetrics.metrics.find(metric => metric.name === metricId )
);
const dispatch = useDispatch();
useEffect(() => {
dispatch(appReducer.actionCreators.fetchMetric(metricId));
}, []);
return (
<div>
<Link to={`/`}>Go back to main page</Link>
Here should print the name of the selected metric:{" "}
<strong>{selectedMetric?.name}</strong>
</div>
);
};
export default MetricDetailsPage;
But you should re-work your action creators so that you aren't selecting the whole catalog in the component and then passing it as an argument to the action creator.

How to retrieve, store, and update data in Firestore using React Hooks

I'm trying to retrieve data (let's say a list of todos) from Firestore and render it to the DOM, but I'm unsure how to properly do this/what the best practice is. Do I retrieve the data from Firestore using useEffect and onSnapshot and store it in state? When adding, deleting, and/or changing data (todos), is it as simple as just using the .add(), .delete(), .update() Firestore methods to update the database, and thus updating the DOM (because of the onSnaphot)?
The code below works, but I'm interested in learning the best way to go about this problem. I heard storing data in state isn't a very good idea so that is another reason I'm asking.
const [ todos, setTodos ] = useState([])
useEffect( () => {
const db = firebase.firestore().collection('todos')
db.onSnapshot( snapshot => {
const retrievedTodos = []
snapshot.forEach( doc => {
retrievedTodos.push({...doc.data(), id: doc.id})
})
setTodos(retrievedTodos)
})
}, [])
return (
<div>
{todos.map( todo => {
return <TodoItem
text={todo.text}
key={todo.id} />
})}
</div>
)

Fetching data in useEffect with an array as dependency should only be called on new elements

I have an array, which is given as prop into my component named Child. Now, every time a new item is added to the array a fetch against an API should be made.
This array is held in a component named Parent using the useState hook. Whenever I want to add a new item, I have to recreate the array, since I'm not allowed to mutate it directly.
I tried to simplify my use case in the following code snippet:
const Parent = () => {
const [array, setArray] = useState([]);
///...anywhere
setArray(a => [...a, newItem]);
return <Child array={array} />;
}
const Child = ({ array }) => {
useEffect(() => {
array.forEach(element => {
fetch(...);
});
}, [array]);
return ...;
}
Now, my question is: How can I achieve to fetch new data from my API only for the new element but not for the whole array again?
I hope I described my issue good enough. If anything is unclear or missing, let me know.
How about instead fetching the API data in Parent and just passing the end result to Child? That refactoring would provide some benefits:
Parent owns the items array state and knows when and where a new item is added. That makes an incremental fetch very easy. You also get division of container and presentational components for free.
The fetched API data is related to the items array. You probably want to use them together in some way (save api data as state, combine them, etc.). This constellation would promote derived state, which is a more error prone pattern.
Something like following example could already do what you want - add an item via onClick (or somehow else), fetch its data and pass the whole array down to Child:
const Parent = () => {
const [array, setArray] = useState([]);
return (
<div onClick={addItem}>
<Child array={array} />;
</div>
);
function addItem(e) {
const item = createItemSomehow(...)
fetch(...).then(data => setArray([...array, { item, data }]));
}
};
Update:
If you want to keep your structure and API as is, an alternative would be to memoize the previous arrays prop in your Child with a usePrevious hook and look for item changes.
const Child = ({ array }) => {
const prevArray = usePrevious(array);
useEffect(() => {
if (array !== prevArray && array.length) {
//fetch(...)
console.log(`fetch data for index ${array.length - 1}`);
}
});
return (...);
};
function usePrevious(value) {
const ref = useRef();
useEffect(() => {
ref.current = value;
});
return ref.current;
}
Codesandbox
You could, for example, keep a list of previously fetched items.
const Child = ({ values }) => {
const [fetched, setFetched] = useState([]);
useEffect(() => {
values.forEach(v => {
if (!fetched.includes(v)) {
setFetched(fetched => [...fetched, v]);
fetch(v);
}
});
}, [values, logged]);
https://codesandbox.io/s/affectionate-colden-sfpff

Resources