I'm trying to set a state. Here is my code:
import React, { Component, useRef, Fragment, useEffect, useState } from "react";
import axios from "axios";
const Cart = (props) => {
const [useCart, setCart] = useState([]);
useEffect(() => {
(async () => {
const result = await axios
.get('https://example.com/sample')
.then((res) => {
setCart(res.data);
})
.catch((err) => {
console.error(err);
});
})();
}, []);
console.log(useCart);
return (
<div></div>
);
}
export default Cart;
The API returns value like that:
[
{
'id': 5,
'qwe': 'qwe',
'asd': [
{
'aaa': 'aaa',
'bbb': 'bbb'
}
],
'zxc': 'zxc'
},
{
'id': 7,
'qwe': 'qwe',
'asd': [
{
'aaa': 'aaa',
'bbb': 'bbb'
}
],
'zxc': 'zxc'
}
]
I'm not rendering this on component. I'm just trying to console log it. But it give error like that:
Error: Objects are not valid as a React child (found: object with keys {id, qwe, asd, zxc}). If you meant to render a collection of children, use an array instead.
I couldn't understand, where do I do wrong.
Don't know what's going wrong with your code but maybe I can help you debug it. I copied your API's response to a file and simply imported it inside the component to a variable. I then set the state to that variable and everything works fine, all console logs and everything. Check it out here-
https://codesandbox.io/s/angry-ramanujan-3k97v?file=/src/App.js
That tells me the problem could be coming from your useEffect where you are handling your API response.
One change that I made and I think you should do it too when you logout a state variable you should do it inside a lifecycle hook. So you should put your console.log(useCart); inside this
useEffect(()=>{ console.log(useCart); },[useCart])
Related
My react is 18.2. I want the child component to receive data and use map() from App.js.
I checked the child component received the data, but I don't know why does it can't use map().
It shows this error.
Uncaught TypeError: Cannot read properties of undefined (reading 'map')
So do anyone can fix it? I guess this is render problem, but I don't know how to fix it, thank you.
fetched():
[
{ id:1, date: '2011-01-01T00:00:00.000Z' },
{ id:2, date: '2013-09-03T00:00:00.000Z' },
{ id:3, date: '2012-04-02T00:00:00.000Z' },
{ id:4, date: '2013-12-08T00:00:00.000Z' },
{ id:5, date: '2010-01-23T00:00:00.000Z' },
];
App.js:
import { Child } from './Child';
const Test = () => {
const [toChild, setToChild] = useState()
useEffect(() => {
const data = async () => {
const fetchedData = await fetched();
setToChild(fetchedData)
};
data();
}, []);
const test = () => {
setToChild(fetchedData)
}
}
return(<Child toChild={toChild}>)
Child.js:
const Child = ({ toChild }) => {
const data = toChild;
const getDate = data.map((item) => item.date);
Since the data coming from your fetch is an array of objects, you may want to initialize the state as an empty array useState([]), useEffect will fire once the component has been mounted so at first, toChild will be undefined and that's why you are getting that error.
So basically:
import { useState, useEffect } from 'react'
import { Child } from './Child';
const Test = ()=>{
const [toChild,setToChild] = useState([])
useEffect(() => {
const data = async () => {
const fetchedData = await fetched();
setToChild(fetchedData)
};
data();
}, []);
return <Child toChild={toChild} />
Jsx is already rendered before javascript in react even though you fetch the data from server but html already rendered to the browser.so add any loading component to that. toChild?:<div>loading</div>:<Child toChild={toChild}/>
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.
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.
I'm trying to create a custom hook and I have problems with an infinite loop.
There is the piece of code that implements the custom hook on my page:
const handleOnFinish = response => {
const {data} = response
setIsLoading(false)
setTableData(data)
setPage(page)
}
const handleOnInit = () => setIsLoading(true)
useEffectUseCaseTokenValidation({
onFinish: handleOnFinish,
onInit: handleOnInit,
params: {nameToFilter: nameFilter, page},
useCase: 'get_clients_use_case'
})
And this is my custom hook:
import {useContext, useEffect} from 'react'
import Context from '#s-ui/react-context'
const noop = () => {}
export function useEffectUseCaseTokenValidation({
onFinish = noop,
onInit = noop,
params = {},
useCase = ''
}) {
const {domain} = useContext(Context)
const config = domain.get('config')
useEffect(() => {
onInit()
domain
.get(useCase)
.execute(params)
.then(response => {
const {error} = response
if (error && error.message === 'INVALID_TOKEN') {
window.location.replace(config.get('LOGIN_PAGE_URL'))
}
onFinish(response)
})
}, [params]) // eslint-disable-line
}
With this, the useEffect is released again and again, instead of taking params into account. I add a console.log for params and is always receiving the same.
I was using this useCase correctly without the custom hook, so that is not the problem.
I want to use this custom hook to avoid to copy and paste the redirection on all UseEffects for all project pages.
Thank you!
The problem is the object ref, that means that you are passing {nameToFilter: nameFilter, page} as params but each time the components renders a new object ref is creating so, react compare both with the ===, so if you run this code in your console
var params1 = { name: 'mike', age: 29 };
var params2 = { name: 'mike', age: 29 };
console.log(params1 === params2); // it will console false
that's because object declaration are not the same event when its key/value pairs are the same.
So to avoid infinite loop into your hook, you should use useMemo to avoid that, so try this
import { useMemo } from 'react';
const params = useMemo(() => ({ nameToFilter: nameFilter, page }), [nameFilter, page])
useEffectUseCaseTokenValidation({
onFinish: handleOnFinish,
onInit: handleOnInit,
params: params,
useCase: 'get_clients_use_case'
})
useMemo will avoid recreating object reft in each render phase of your component
Please read the useMemo react official docs
Please read this post to know the differences between values VS references comparison
Trying to get along with React new Hooks and ActionCable, but stuck with the problem that I can't get the right data in Rails when trying to send state.
I've tried to use send() method immediately after doing setState() and send my updated data, but for some reason, the data which received on the Rails part is old.
For example, if I put "Example" to the input I'll see "{"data"=>"Exampl"} on the Rails side. I suppose the data update the state later than my request goes.
If I send() value from e.target.value everything works fine
Therefore I've tried to use new useEffect() hook and send data there. But I get only data when rendering the page. Afterward, I don't get anything and sometimes get error RuntimeError - Unable to find subscription with an identifier. Seems like effect hook sends data too early or something.
I'm pretty new to Hooks and WebSockets. Would love to get any help here. I can share Rails code, but there is only a receiver and nothing else.
First exmaple:
import React, { useState, useEffect } from "react"
import ActionCable from 'actioncable'
function Component(props) {
const [data, setData] = useState("");
const cable = ActionCable.createConsumer('ws://localhost:3000/cable');
const sub = cable.subscriptions.create('DataChannel');
const handleChange = (e) => {
setData(e.target.value)
sub.send({ data });
}
return (
<input value={data} onChange={handleChange}/>
)
}
Tried to useEffect and move send() there:
useEffect(() => {
sub.send({ data });
}, [data]);
I'd love to find a way to correctly use React and ActionCable. And use hooks if it's possible.
I was trying an approach similar to Oleg's but I could not setChannel inside the action cable create subscription callback. I had to setChannel outside of the callback but within the useEffect hook. Below is the solution that worked for me.
create consumer in index.js and provide the consumer through Context to App.
index.js
import React, { createContext } from 'react'
import actionCable from 'actioncable'
... omitted other imports
const CableApp = {}
CableApp.cable = actionCable.createConsumer('ws://localhost:3000/cable')
export const ActionCableContext = createContext()
ReactDOM.render(
<Router>
... omitted other providers
<ActionCableContext.Provider value={CableApp.cable}>
<App />
</ActionCableContext.Provider>
</Router>,
document.getElementById('root')
)
Use the cable context in your child component and create subscription in useEffect hooks; unsubscribe in clean up
import React, { useState, useEffect, useContext } from 'react'
import { useParams } from 'react-router-dom'
... omitted code
const [channel, setChannel] = useState(null)
const { id } = useParams()
const cable = useContext(ActionCableContext)
useEffect(() => {
const channel = cable.subscriptions.create(
{
channel: 'MessagesChannel',
id: id,
},
{
received: (data) => {
receiveMessage(data)
},
}
)
setChannel(channel)
return () => {
channel.unsubscribe()
}
}, [id])
const sendMessage = (content) => {
channel.send(content)
}
You can register your cable at root component like that:
import actionCable from 'actioncable';
(function() {
window.CableApp || (window.CableApp = {});
CableApp.cable = actionCable.createConsumer('ws://localhost:3000/cable')
}).call(this);`
so it will be available as global variable;
and then in any component where you want to create channel and send data:
const [channel, setChannel] = useState(null);
useEffect(() => {
CableApp.cable.subscriptions.create(
{
channel: 'YourChannelName',
},
{
initialized() {
setChannel(this)
},
},
);
}, []);
return <button onClick={() => channel.send(some_data)} >Send counter</button>
Your problem is here:
const handleChange = (e) => {
setData(e.target.value)
sub.send({ data });
}
setData is like setState in that the state is only updated after the render i.e. after the function has exited. You are sending the current data not the new data. Try this:
const handleChange = (e) => {
const newData = e.target.value;
setData(newData)
sub.send({ data: newData });
}