Destructured object as function parameter - javascript

I don’t understand the parameter of const Posts below. I’m fairly new to node/React. Is it a destructured parameter object? Or is it just an object being passed as a parameter?
getPosts and post are showing as undefined. But I don’t understand where the parameter object is being passed from into the function...
Full code here: https://github.com/bradtraversy/devconnector_2.0/blob/master/client/src/components/posts/Posts.js
Thanks in advance!!
import React, { Fragment, useEffect } from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import Spinner from '../layout/Spinner';
import PostItem from './PostItem';
import PostForm from './PostForm';
import { getPosts } from '../../redux/actions/post';
const Posts = ({ getPosts, post: { posts, loading } }) => {
useEffect(() => {
getPosts();
}, [getPosts]); ```

So Posts is a React Function component.
All Function components will receive a props object as its first argument.
const Posts = (props) => { /* ... */ }
props will always be an object containing the props that were passed into it when the component was rendered, for example:
import Posts from './path/to/Posts'
function SomeParentComponent() {
return <Posts limit={10} categories={{news:true, sports:false}} />
}
In this case props will be an object that looks like this:
{
limit : 10,
categories : {
news : true,
sports : false,
}
}
You can of course destructure the props object in your component:
const Posts = (props) => {
const {
limit,
categories
} = props
// ... other stuff
}
But you can go even further and do what's called "unpacking" in order to destructure nested properties
const Posts = (props) => {
const {
limit,
categories : {
sports,
news
}
} = props
// ... other stuff
}
Lastly, instead of doing that in the function body, you can destructure and unpack objects in-line where the arguments are for the same result.
const Posts = ({limit, categories:{news,sports}}) => {
// ... other stuff
}
Which is what your code sample is doing.
It appears it's expecting the parent component to pass in a function as the getPosts prop, which when called will first set posts.loading to true, load the posts, then set posts.loading to false. Ex:
function SomeParentComponent() {
const [loading, setLoading] = useState(false)
const [posts, setPosts] = useState([])
const loadPosts = useCallback(async () => {
setLoading(true)
const loadedPosts = await loadPostsSomehow()
setPosts([posts, ...loadedPosts])
setLoading(false)
}, [])
return <Posts getPosts={loadPosts} post={{posts, loading}} />
}
Make sure to use useCallback to get a memoized callback here or you will get stuck in an infinite loop
**EDIT**
After actually looking at the link provided, it's slightly different actually. Instead of the post object being provided by the parent component, it's actually provided by redux, but the logic is essentially the same. The difference is that instead of the parent component changing the loading and post state, it's done via redux state management.

Yes, it is de-structured function parameter object.
In your case parameters to Posts pass through Redux connect() function.
const mapStateToProps = state => ({
post: state.post
});
export default connect(
mapStateToProps,
{ getPosts }
)(Posts);
Check your getPosts import and make sure it is not undefined.
import { getPosts } from '../../actions/post';
Also check your redux state and make sure it has state.post.

Related

no data is passed into state when using useContext/useReducer together with useQuery

export const itemReducer = (state, action) => {
switch (action.type) {
default:
return state
}
}
import React, { useState, useReducer, createContext, useContext } from 'react'
import { useQuery } from '#apollo/client'
import { CURRENT_MONTH_BY_USER } from '../graphql/queries'
import { itemReducer } from '../reducers/ItemReducer'
const Items = createContext()
export const ItemProvider = ({ children }) => {
let items = []
const [state, dispatch] = useReducer(itemReducer, { items: items })
const result = useQuery(CURRENT_MONTH_BY_USER)
if (result.data && result.data.getCurrentMonthByUser) {
items = [...result.data.getCurrentMonthByUser]
}
return <Items.Provider value={{ state, dispatch }}>{children}</Items.Provider>
}
export const ItemsState = () => {
return useContext(Items)
}
export default ItemProvider
let items gets correct data from the useQuery, however nothing is passed into the state, therefore I am unable to transfer data into another components from the context. What am I doing wrong here?
When debugging both items and state they're initially empty because of the loading however then only the items receives correct data and state remains as empty array.
If i put static data into let items it works just fine, so maybe there can be something wrong with my useQuery as well?
It's easy to see your problem if you look at where items is used. That's only as the initial state to your useReducer call - but items is only set to a non-empty value after this. That has absolutely no effect on the component, because items is not used later in your component function, and the initial state is only ever set once, on the first render.
To solve this you need to embrace your use of a reducer, adding a new action type to set this initial data, and then dispatching that when you have the data. So add something like this to your reducer:
export const itemReducer = (state, action) => {
switch (action.type) {
case SET_INITIAL_DATA: // only a suggestion for the name, and obviously you need to define this as a constant
return { ...state, items: action.items };
/* other actions here */
default:
return state
}
}
and then rewrite your component like this:
export const ItemProvider = ({ children }) => {
const [state, dispatch] = useReducer(itemReducer, { items: [] })
const result = useQuery(CURRENT_MONTH_BY_USER)
if (result.data && result.data.getCurrentMonthByUser) {
dispatch({ type: SET_INITIAL_DATA, items: result.data.getCurrentMonthByUser });
}
return <Items.Provider value={{ state, dispatch }}>{children}</Items.Provider>
}
Also, while this is unrelated to your question, I will note that your ItemsState export appears to be a custom hook (it can't be anything else since it isn't a component but uses a hook) - that is perfectly fine but there is a very strong convention in React that all custom hooks have names of the form useXXX, which I strongly suggest you should follow. So you could rename this something like useItemsState (I would prefer useItemsContext to make clear it's just a useContext hook specialised to your specific context).

How to Refactor an Async Function out of a React Component

I have a straightforward react component that looks so in AllWords.js :
import React, { useEffect, useState } from 'react';
import consts from '../../constants/Constants.js';
function AllWords() {
const [words, setWords] = useState([]);
async function fetchData(){
const response= await fetch(consts.FETCH_URL);
const data = await (response.json());
setWords(data);
};
// API: useEffect( () => { . . . return cleanup; },[var_n_whose_change_triggers_useEffect . . .] );
useEffect(() => {fetchData()}, [] );
return (
<>
{
words.map(w=> <div>{w.word}</div>)
}
</>
);
}
export default AllWords;
I would like to refactor the fetchData() method out of the component into another file (basically a separate .js file that holds the fetch call).
What I would like is to have created a file titled FetchAllWords.js under src/actions/ & then import it. & use that.
I have several questions :
do I need to set the state in the FetchAllWords.js and then useSelector to extract the state in AllWords.js?
in FetchAllWords.js do I need to usedispatch to dispatch a method call setting the state? I would like to just setState in FetchAllWords.js and then extract it in AllWords.js. This is what I have so far:
import consts from '../constants/Constants.js';
import { useState } from 'react';
async function FetchAllWords(){
const [words, setWords] = useState([]);
const response= await fetch(consts.FETCH_URL);
const data = await (response.json());
setWords(data);
}
export default FetchAllWords;
I am unsure how to import this and use it in AllWords.js. I am using the following statement :
import wordList from '../../actions/FetchAllWords';
Then I am trying to use wordList as a handle to the file '../../actions/FetchAllWords.js' & attempting to access the async function FetchAllWords so wordList.FetchAllWords();
Firstly , the editor (VSCode) won't let me see the function despite the import call.
Secondly I am getting an error (something like) :
TypeError: _actions_FetchAllWords_js__WEBPACK_IMPORTED_MODULE_3__.default.FetchAllWords is not a function
Any insight or help would be appreciated since rather uneasy with JS & React.
The github repo is : https://github.com/mrarthurwhite/hooks-p5-react-redux
EDIT: As per David's suggestions :
So AllWords.js React component is :
import React, { useEffect, useState } from 'react';
import wordList from '../../services/Fetch.js';
function AllWords() {
const [words, setWords] = useState([]);
function fetchData(){
wordList.fetchAllWords().then(
data => setWords(data)
);
};
// API: useEffect( () => { . . . return cleanup; },[var_n_whose_change_triggers_useEffect . . .] );
useEffect(() => {fetchData()}, [] );
return (
<>
{
words.map(w=> <div>{w.word}</div>)
}
</>
);
}
export default AllWords;
And Fetch.js is :
import consts from '../constants/Constants.js';
class Fetch {
async fetchAllWords(){
const response= await fetch(consts.FETCH_URL);
const data = await (response.json());
return data;
}
}
export default Fetch;
No, don't worry about state in the external file. Just focus on the one thing it should do, perform the AJAX operation. At its simplest it's just a function, something like:
import consts from '../../constants/Constants.js';
const fetchAllWords = async () => {
const response = await fetch(consts.FETCH_URL);
const data = await (response.json());
return data;
}
export default fetchAllWords;
You can even make it a class which contains this function, if you plan on adding other service operations as well. (Fetch specific word? Find word? etc.) The point is that this does just one thing, provide data. Let the React components handle React state.
Within the component you'd just use that to get your data. Something like:
import React, { useEffect, useState } from 'react';
import fetchAllWords from '../../services/FetchAllWords.js';
function AllWords() {
const [words, setWords] = useState([]);
useEffect(() => {
fetchAllWords().then(w => setWords(w));
}, []);
return (
<>
{
words.map(w=> <div>{w.word}</div>)
}
</>
);
}
export default AllWords;
Overall it's a matter of separating concerns. The service performs the AJAX operation and returns the meaningful data, internally concerned with things like JSON deserialization and whatnot. The React component maintains the state and renders the output, internally concerned with updating state after useEffect runs and whatnot.

How to use Hooks inside useEffect?

I wrote a demo here:
import React, { useRef, useEffect, useState } from "react";
import "./style.css";
export default function App() {
// let arrRef = [useRef(), useRef()];
let _data = [
{
title: A,
ref: null
},
{
title: B,
ref: null
}
];
const [data, setData] = useState(null);
useEffect(() => {
getDataFromServer();
}, []);
const getDataFromServer = () => {
//assume we get data from server
let dataFromServer = _data;
dataFromServer.forEach((e, i) => {
e.ref = useRef(null)
});
};
return (
<div>
{
//will trigger some function in child component by ref
data.map((e)=>(<div title={e.title} ref={e.ref}/>))
}
</div>
);
}
I need to preprocess after I got some data from server, to give them a ref property. the error says 'Hooks can only be called inside of the body of a function component' . so I checked the document, it says I can't use hooks inside a handle or useEffect. so is there a way to achieve what I need?
update:
I need to create component base on DB data, so when I create a component I need to give them a ref , I need trigger some function written in child component from their parent component and I use ref to achieve that. that is why I need to pass a ref to child component.

How to NOT persist state between Next.js dynamic routes?

I'm building a headless eCommerce site using React/Next and have a [product].js dynamic route which is used to generate all product pages, using getStaticPaths() and getStaticProps() which generates the pages fine.
I'm using useState hook within [product].js to manage a number input (for quantity) and a couple of other things.
The first product page loaded works fine, but when I go to other product pages, they use the same state from the first product.
Is there a way to have the state NOT persist between route changes?
Through some digging, I found that this is an issue with next and is in their backlog. It essentially stems from the fact that the component doesn't have a key. This means switching between routes on the same dynamic route doesn't register correctly and causes the component to use stale state.
A possible solution I found was this:
export async function getStaticProps({params}) {
const props = await getData(params);
// key is needed here
props.key = data.id;
return {
props: props
}
}
This is my implementation which doesn't work for me:
export default function ProductPage(props) {
// this state doesn't reset between dynaic route changes
const [quantity, setQuantity] = useState(1)
return(
...
)
}
export async function getStaticProps({ params }) {
const slug = params.product
const props = await client.query({
query: singleProductQuery,
variables: { id: slug }
})
props.key = props.data.product.slug
return {
props: props
}
}
I tried wrapping the contents within another component and adding a key to that, like so:
return(
<OuterComponent key={props.id}>
// components within here, that have their own state, now work
</OuterComponent>
)
Since this new keyed component is only in the return statement and does not encapsulate the state hook, it does not work. This does reset the state however, for any components found within wrapped component.
You can use useEffect hook and useRouter hook at dynamic router to reset the state.
import {useState, useEffect} from 'react'
import {useRouter} from 'next/router'
const ProductPage = (props) => {
const [state, setState] = useState(someState)
const dynamicRoute = useRouter().asPath
useEffect(() => {
setState(resetState) // When the dynamic route change reset the state
}, [dynamicRoute])
//Some other logic
return (
......
)
}
It seems that you've encountered the same issue thread that I've found:
https://github.com/vercel/next.js/issues/9992
It seems from what I've read that to fix your case, all you need to do is change your getStaticProps to return an object with a unique key:
export async function getStaticProps({ params }) {
const slug = params.product
const props = await client.query({
query: singleProductQuery,
variables: { id: slug }
});
return {
props: props,
key: slug
}
}
What you've been doing previously is passing a key to the props object instead of root return object for getStaticProps
You can use useEffect hook to reset state
export default function ProductPage(props) {
// this state doesn't reset between dynaic route changes
const [quantity, setQuantity] = useState(1)
useEffect(() => {
setQuantity(props.quantity) // <-- this props comes from getStaticProps
}, [props]) // <--- useEffect will keep tracking changing props
return(
...
)
}
So when your props changes - your state updates.

React Hook useEffect has a missing dependency, using Redux

Should I ignore 'React Hook useEffect has a missing dependency' warning?
Usually when I am getting data from an API this is what I do:
const Component = () => {
const [data,setData] = useState([]);
const getData = () => {
//Getting data and set data code...
}
useEffect(()=>{
getData();
},[]);
}
and recently I am trying out use redux to do the same thing(getting data from API) and I got this 'React Hook useEffect has a missing dependency' warning...
action:
import {GET_POSTS} from './types';
const getPosts = () => (dispatch) => {
const url = 'https://jsonplaceholder.typicode.com/posts';
fetch(url)
.then(res => res.json())
.then(data => {
dispatch({
type: GET_POSTS,
payload: data
});
});
}
export default getPosts;
reducer:
import {GET_POSTS} from '../actions/types';
const initialState = {
posts: []
}
const postsReducer = (state = initialState, action) => {
switch(action.type){
case GET_POSTS:
return {
...state,
posts: action.payload
}
default:
return state;
}
}
export default postsReducer;
app.js:
import React, {useEffect} from 'react';
import {connect} from 'react-redux';
import Hello from './components/Hello';
import getPost from './actions/postsAction';
import './App.css';
const App = ({getPost, dispatch}) => {
useEffect(() => {
getPost();
},[]);
return (
<div className='App'>
<Hello/>
</div>
);
};
const mapdispatchtoprops = (dispatch) => ({
dispatch,
getPost: () => {
dispatch(getPost());
}
});
export default connect(null, mapdispatchtoprops)(App);
Is there a way to fix this problem, I have tried to put dispatch inside the useEffect array but the warning still shows, like this:
useEffect(() => {
getPost();
},[dispatch]);
This is the full warning: React Hook useEffect has a missing dependency: 'getPost'. Either include it or remove the dependency array react-hooks/exhaustive-deps
Tried to remove the useEffect array but I'll get infinite loop, it'll just keeps getting the data from the api(I only need it to run once).
Should I ignore the warning? if not, whats the best practice way to handle this problem?
I never got this kind of warning before when I left the useEffect array empty but got it recently, why?
The error message is telling you what you to do. Just add getData to the dependencies array like so: [dispatch, getData]. Anything external you reference within your useEffect (like a function) should be part of the dependency list so it can trigger the effect whenever the value changes. In your case it likely won't, but React is warning you just to be safe. Hope that helps!
You may want to start thinking from a different perspective. You are apparently trying to do side effect of loading data after component got rendered. So just inject your data via redux or propagation props from parent and remove array altogether. I.e.
const Component = ({posts}) => {
const getData = () => {
//Getting data and set data code...
}
useEffect(() => {
if (!posts) {
getData();
}
});
....
}
Your posts will be loaded once and useEffect's function should only care about posts is there or not.

Categories