setState() With Object.keys() Through Fetch Requests In Parent Component - javascript

I am trying to set the state with data received from a fetch request in the parent component. I am receiving an array of objects that each have the following keys: ‘name’, ‘artist’, ‘url’, ‘cover’, ‘lrc’, and ‘theme’. I am using Object.keys() to map over the object data, but I am wondering how I can set the state in this way so as to have multiple objects with those six keys be stored in the state so my state will look like:
this.state = { data: [{ {…}, {…}, {…}, etc… }] }
One big issue is that my data - from a fetch request in the parent - is not rendering in this tempComp component. I am passing the data in as a prop (this.props.playlist). Why is the fetched data in the parent not rendering in the tempComp component, and how can I set state with multiple objects, as I attempted below with Object.keys()? Any advice is greatly appreciated.
import React, { Component } from 'react'
class tempComp extends Component {
constructor(props) {
super(props);
this.state = {
data: [],
audio: [{
name: '',
artist: '',
url: '',
cover: '',
lrc: '',
theme: ''
}]
}
}
async componentDidMount() {
console.log('playlist in componentDidMount()', this.props.playlist) //<--- AudioPlayer data should be coming in here
var json = this.props.playlist;
var arr = [];
Object.keys(json).forEach(function (key) {
arr.push(json[key]);
});
arr.map(item => {
this.setState({
audio: [{
name: item.name,
artist: item.artist,
url: item.url,
cover: item.cover,
lrc: item.lrc,
theme: item.theme
}]
})
})
console.log(this.state.audio);
}
render() {
return (
<div>
</div>
)
}
}
export default tempComp
And here is the parent component, for clarification:
export default class PostContent extends Component {
constructor(props) {
super(props);
this.state = {
id: '',
episodeData: [],
}
}
async componentDidMount(){
const { id } = this.props.match.params;
const response = await fetch(`http://localhost:5000/episode/${id}/playlist`);
const jsonData = await response.json();
this.setState({
episodeData: jsonData, //this is working!...
id: id
});
console.log(this.state.episodeData) //...see?
}
render() {
return (
<Fragment>
<TempComp playlist={this.state.episodeData} />
<AudioPlayer playlist={this.state.episodeData} />
<Link id='home-link' to='/' activeClassName='active'>Homepage</Link>
{this.state.episodeData.map((item, i) => (
<div key={i} className="word-content">
<h2 className="show-title">{item.post_title}</h2>
<div className="episode-post-content">{item.post_content}</div>
</div>
))}
<Table data={this.state.data} />
<div className="bottom-link">
<Link id='home-link' to='/' activeClassName='active'>Homepage</Link>
</div>
</Fragment>
)
}
}

You've been a bit tricked here with the way setState works. It's an asynchronous method which means it's not going to run in tandem with your map. You could use a setTimeout or an await which I can see is possible given your componentDidMount is prefixed by async. However, if I can take the liberty, i'd recommend you make some small changes like so:
const json = {
apple: { yum: false },
peach: { yum: true }
};
const yummyness = Object.keys(json).map(key => {
return { yum: json[key].yum }
});
// yumminess will render: [{ yum: false }, { yum: true }]
this.setState({ yummyness });
What I've done here is a few things:
If data isn't changing, assign it to a const, you can learn more about that here
Map returns an array. This can be really handy such as now, so instead of pushing to your arr value, I've just returned an object to prevent you doing your second map.
Finally, as I mentioned earlier setState is asynchronous and a bit of a slippery sucker! So to avoid that I'm just letting the map do it's thing and THEN I've assigned it to yummyness.
Bonus Round: I've used fruit but I've left most of your naming the same. json is your props.playlist and yumminess is your arr
Hope this helped!

Related

How to change the value in Constructor ReactJs

My problem is that the code is working correctly
I would like to be able to change the value val: 'yolo' by either a component from another page or direct by my database
Do you have an idea, how to fix this? Neff
import React from 'react'
import axios from 'axios'
const entrypoint = process.env.REACT_APP_API_ENTRYPOINT + '/api';
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
data: [],
};
this.clickHandler = this.clickHandler.bind(this)
this.state = {currentPosition: 0, totalLength: 3, val: 'yolo'}
}
getRandom = async () => {
const res = await axios.get(
entrypoint + "/alluserpls"
)
this.setState({ data: res.data })
}
componentDidMount() {
this.getRandom()
}
clickHandler(){
this.setState({currentPosition: (this.state.currentPosition + 1)%this.state.totalLength})
}
render() {
return (
<div >
<button onClick={this.clickHandler} >Move to the Right</button>
{
Array.from(
{length: this.state.totalLength},
(_,i) => (
<div key={i} className="slot">
<p>{i === this.state.currentPosition ? this.state.val : null}</p>
</div>
)
)
}
</div>
)}
}
export default App;
one way you can change the value of Yolo similar way as you are getting data from the server.
as for changing it from another component , you can do it by either getting it as a props from its parent component where you use this component
<App yoloVal = {"yoloValue"}/>
and you can receive it in props either when it mounts or when it updates
componentDidMount(){
this.setState({
yolo : this.props.yoloVal
}
}
or when it updates
componentDidUpdate(){
if(this.props.yoloVal !== prevProps.yoloVal){
this.setState({
yolo : this.props.yoloVal
}
}
}
you can also get this value from a child in the App by passing it a method
write a method in the App Component
setYoloValue(val){
this.setState({
yolo : val
}
}
now pass this method in render method of App to a child component
return (
<ChildComponent setYoloValue = {this.setYoloValue.bind(this)}
)
we are using bind so when this method is called the context remains of the parent instead of the caller(child component)
now you can use this method anywhere in the child to set the value of Yolo on parent
class ChildComponent extends Component {
componentDidMount(){
this.props.setYoloValue("new Yolo Value by child")
}
}
Now as for passing data between siblings , you can give the data by using the above two methods , first have a common parent , pass the data to parent by using second method then pass that data parent received to the other children as the first method. that is how you can acheive communication between siblings components.
as for setting the value from any other component in the app that is not directly related to you component , you need Redux or similar that does the job for you by keeping the values in a common store and components listen to that store and receive the update when the value in the store updates.
I would like to be able to change the value val: 'yolo'
1.by either a component,
2.from another page
3.or direct by my database
i'm actually surprised by the following piece of code, and not even sure, it 's a valid one. you are initializing this.state twice inside your constructor.
constructor(props) {
super(props);
--> this.state = {
data: [],
};
this.clickHandler = this.clickHandler.bind(this)
--> this.state = {currentPosition: 0, totalLength: 3, val: 'yolo'}
}
you initialize your entire variables inside your constructor..
constructor(props) {
super(props);
this.state = {
data: [],
currentPosition: 0,
totalLength: 3,
val: 'yolo',
};
this.clickHandler = this.clickHandler.bind(this)
}
idea is to pass a function(prevState) as a callback to update the local state so as to escape batching.
getRandom = async () => {
const res = await axios.get(
entrypoint + "/alluserpls"
)
this.setState(prevState => ({
...prevState,
data: res.data,
}))
}
i'm not sure this will work as you expected..
clickHandler(){
this.setState({currentPosition: (this.state.currentPosition + 1)%this.state.totalLength})
}
since you are doing a division, it's good to Math.floor or ceil(you need to find whichever value meets your requirement.)
//1. by a component..
handleValChange(val) => {
this.setState(prevState => ({
...prevState,
val,
}))
}
//now u can pass it to a child component.
render() {
const { handleValChange } = this
return (
<div>
<...rest of the div.../>
<ChildComponent {...{ handleValChange }} />
</div>
)
}
from another page.
from another page means, probable a diffrent route. in such cases u need to update this globally(redux, mobx etc..) and the value should also live globally not locally. u can pass id's and stuff via url but function, not possible.
direct by db.
this is where u make an api call and based on the response u update the state. that means, it's time to extract your application into a global state(redux, mobx etc..)
this.state = {
data: [],
};
this.clickHandler = this.clickHandler.bind(this)
this.state = {currentPosition: 0, totalLength: 3, val: 'yolo'}
You should not have two states in one constructor. Change it to one state:
this.state {
data: [],
currentPosition: 0,
totalLength: 3,
val: 'yolo',
}
As for changing the value from another component, there are two easy solutions.
1) Using Redux to handle state, instead of local state, probably the best solution.
2) Use a callback function that call setState in that component, and pass it to the other component, if it is a child of this component.
const myCallbackFunction(value: string) {
this.setState({ val: value })
}

how to dispatch same action with different params in react redux

I would like to know how to dispatch action with different params in react redux.
In format.js, queryData with different param is called by callFetch method.
After props dispatch, in the render method, will receive data in props memberData,
I receive the response memberDatawith status Inactive but
How to display for both the Active and Inactive,
// format.js
componentDidMount(){
this.callFetch();
}
callFetch(){
this.props.dispatch(queryData(this.createFetchUrl("Active")));
this.props.dispatch(queryData(this.createFetchUrl("Inactive")));
}
createFetchUrl(status) {
const body ={
position: "civil",
status: status
}
return body;
}
render(){
const { memberData } = this.props.data
return(
<div>
Active
<p>{memberData}</p>
Inactive
<p>{memberData}</p>
</div>
)
}
//actions.js
export const queryDeviceByMember = payload =>
queryApi({
request: CONSTANTS.DATA,
url: `api/details`,
encode: false,
success: CONSTANTS.DATA_SUCCESS,
failure: CONSTANTS.DATA_FAILURE,
...payload
});
//reducer.js
case CONSTANTS.DATA_SUCCESS:
case CONSTANTS.DATA_FAILURE:
return {
...state,
memberData: data,
apiPending: false,
errormsg: errormsg,
servererror: servererror || ""
};
am receiving the same response for both active and inactive states, since it overrides, without overriding how to do
[{
id: "20",
name: "xxx",
device: "ios"
},
{
id: "10",
name: "yyy",
device: "ios"
}]
You are overwriting the data in memberData, consider following these steps:
Split your reducers for active and inactive cases
Use a selector to select the data from your state, i.e:
export const getActiveMembers = state => state.activeMembers;
export const getInactiveMembers= state => state.inactiveMembers;
Import the selectors from step 2 into your components, and use mapStateToProps(state) to get the data from your state:
function mapStateToProps(state) {
return {
activeMembers: getActiveMembers(state),
subscription: getInactiveMembers(state)
};
}
Use the data in render:
render(){
const { memberData } = this.props.data
return(
<div>
Active
<p>{memberData}</p>
Inactive
<p>{memberData}</p>
</div>
)
}
Good Luck
P.S: I would use one API call to get all the members and then classify them to inactive & active in my state using the reducer.

set multiple states, and push to state of array in one onClick function

I'm running into a recurring issue in my code where I want to grab multiple pieces of data from a component to set as states, and push those into an array which is having its own state updated. The way I am doing it currently isn't working and I think it's because I do not understand the order of the way things happen in js and react.
Here's an example of something I'm doing that doesn't work: jsfiddle here or code below.
import React, {Component} from 'react';
class App extends Component {
constructor(props) {
super(props);
this.state = {
categoryTitle: null,
categorySubtitle: null,
categoryArray: [],
}
}
pushToCategoryArray = () => {
this.state.categoryArray.push({
'categoryTitle': this.state.categoryTitle,
'categorySubtitle': this.state.categorySubtitle,
})
}
setCategoryStates = (categoryTitle, categorySubtitle) => {
this.setState({
categoryTitle: categoryTitle,
categorySubtitle: categorySubtitle,
})
this.pushToCategoryArray();
}
render() {
return (
<CategoryComponent
setCategoryStates={this.setCategoryStates}
categoryTitle={'Category Title Text'}
categorySubtitle={'Category Subtitle Text'}
/>
);
}
}
class CategoryComponent extends Component {
render() {
var categoryTitle = this.props.categoryTitle;
var categorySubtitle = this.props.categorySubtitle;
return (
<div onClick={() => (this.props.setCategoryStates(
categoryTitle,
categorySubtitle,
))}
>
<h1>{categoryTitle}</h1>
<h2>{categorySubtitle}</h2>
</div>
);
}
}
I can see in the console that I am grabbing the categoryTitle and categorySubtitle that I want, but they get pushed as null into this.state.categoryArray. Is this a scenario where I need to be using promises? Taking another approach?
This occurs because setState is asynchronous (https://reactjs.org/docs/state-and-lifecycle.html#using-state-correctly).
Here's the problem
//State has categoryTitle as null and categorySubtitle as null.
this.state = {
categoryTitle: null,
categorySubtitle: null,
categoryArray: [],
}
//This gets the correct values in the parameters
setCategoryStates = (categoryTitle, categorySubtitle) => {
//This is correct, you're setting state BUT this is not sync
this.setState({
categoryTitle: categoryTitle,
categorySubtitle: categorySubtitle,
})
this.pushToCategoryArray();
}
//This method is using the state, which as can be seen from the constructor is null and hence you're pushing null into your array.
pushToCategoryArray = () => {
this.state.categoryArray.push({
'categoryTitle': this.state.categoryTitle,
'categorySubtitle': this.state.categorySubtitle,
})
}
Solution to your problem: pass callback to setState
setCategoryStates = (categoryTitle, categorySubtitle) => {
//This is correct, you're setting state BUT this is not sync
this.setState({
categoryTitle: categoryTitle,
categorySubtitle: categorySubtitle,
}, () => {
/*
Add state to the array
This callback will be called once the async state update has succeeded
So accessing state in this variable will be correct.
*/
this.pushToCategoryArray()
})
}
and change
pushToCategoryArray = () => {
//You don't need state, you can simply make these regular JavaScript variables
this.categoryArray.push({
'categoryTitle': this.state.categoryTitle,
'categorySubtitle': this.state.categorySubtitle,
})
}
I think React doesn't re-render because of the pushToCategoryArray that directly change state. Need to assign new array in this.setState function.
// this.state.categoryArray.push({...})
const prevCategoryArray = this.state.categoryArray
this.setState({
categoryArray: [ newObject, ...prevCategoryArray],
)}

Rendering with recursion for a nested list in React - how to avoid unnecessary updates?

I have a list object in my redux state. It has a byId set of like this:
{
root: {
id: 'root',
children: ['item_1', 'item_2'],
data: 'Root String',
parent: null,
},
item_1: {
id: 'item_1',
children: ['item_1_1', 'item_1_2', 'item_1_3'],
data: 'Some String',
parent: 'root',
},
item_1_1:
{
id: 'item_1_1'
children: ['item_1_1_1', 'item_1_1_2']
data: 'Some Other string',
parent: 'item_1',
},
item_2: {
...
}
...
}
Now, I'm rendering the above list like the following:
render()
recursiveRender = (component, idx = 0) => {
const { data, children } = component;
const { list } = this.props;
return (
<div key={idx}>
{
children.map((child, childIdx) =>
this.recursiveRender(list[child], childIdx))
}
</div>
);
};
render() {
const { list } = this.props;
return this.recursiveRender(list.root);
}
Redux Reducer
I want to update the order of the children of, let's say, item_1.
I do the following in my redux action:
import update from 'immutability-helper';
...
...
return update(state,
{
list: {
[parent]: {
children: {
$splice: [[oldIdx, 1], [idx, 0, id]] },
},
},
}
});
which does work.
Problem
I notice in React Dev Tools that every one (item_2 and item_3 too) of the recursively rendered component is repainted. I have no idea why. I'm making sure the list is immutable but the whole tree is rendered again every time. How can I make sure only the changed part (item_1 here) of the list is repainted?
I don't think using reselect is the solution here since the list is changing indeed.
So, should I have a dynamic mapStateToProps? If so, how to not change other list items that depend on the list? Or am I looking at the wrong place and the problem can be elsewhere in the application.
Stateless functional components do not do any comparison between previous and new props. A simple solution would be to use PureComponent for your RecursiveRender instead.

React - prop undefined

I'm currently learning to build apps with React (not using Redux yet). So far everything is working as expected except when it comes to load the data (via ajax). Let me refine what I mean.
I'm using the spotify API using the library "spotify-web-api-js".
So far, I have 2 components:
Search Field
Display the list of Artists
Of course everything is wired up on the App level.
App Level
class App extends Component {
constructor(props) {
super(props);
this.state = {
artists: []
}
this.searchArtists('Chris');
}
searchArtists(artist) {
api.searchArtists(artist, (err, data) => {
if (err) { console.log(err); };
this.setState({ artists: data }, () => {
console.log(this.state.artists);
});
});
}
render() {
const artistSearch = (artist) => { this.searchArtists(artist); };
return (
<div>
<Search onSearchTermChange={ artistSearch } />
<SearchResult artists={ this.state.artists.artists } />
</div>
);
}
};
Search
class Search extends Component {
constructor(props) {
super(props);
this.state = { artist: '' };
}
render() {
return (
<div className="search-bar">
<input
value={ this.state.artist }
onChange={ event => this.onInputChange(event.target.value) }
placeholder="Search by Artist" />
</div>
);
}
onInputChange(artist) {
this.setState({ artist });
this.props.onSearchTermChange(artist);
}
}
export default Search;
Search Results
const SearchResult = props => {
if (!props) {
console.log('loading');
}
return <li></li>;
}
THE PROBLEM
I'm trying to display the results in SearchResult, but it always returns undefined. I tried a settimeout but I know that's the not the solution to my problem.
Why would props return undefined? Even if the data seems to return fine.
I will continue digging for a simple answer and will Edit the question if I find something.
Thanks in advance!
EDIT:
So here is the response I get in the console:
{
artists: {
artists: {
href: 'sikshdksad',
items: [array],
limit: 20,
....
}
}
}
There are 2 artists, because this.state = { artists: [] } if I'm not wrong.
It looks like you're initially defining your artists state as an array:
constructor(props) {
super(props);
this.state = {
artists: []
}
this.searchArtists('Chris');
}
You eventually set that to whatever data returns in your return function. But when you reference the artists data to pass in as a prop, it looks like you're trying to access the artists value on the artists object:
<SearchResult artists={ this.state.artists.artists } />
Maybe just get rid of that last .artists?

Categories

Resources