React component not updating after props change - javascript

I am making a newsapp in react.js following tutorial, now i want to add different categories to my app the man in the tutorial was using react-router-dom for navigating but I want to set a state named category and category as it's key and pass it to news component as props, i have made a function in app.js for selecting category which takes an argument and sets it as category's (the state's) key and i am passing that function to navbar as props, so i can use it in navbar when a category is clicked I can change the state but when i change the category nothing happens some will say use key but that's not the problem because I'm doing it, when I tried console logging the category in the setCategory function it is being logged continuously and the state doesn't change when i click on any of the category what am i doing wrong
my code:
import './App.css';
import React, { Component } from 'react'
import Navbar from './components/Navbar';
import News from './components/News';
export default class App extends Component {
constructor() {
super()
this.state = {
darkMode: "light",
country: "us",
category: "general",
key: "general"
}
}
setCategory = (cat)=> {
this.setState({
category: cat
})
console.log(this.state.category)
}
setCountry = (cntry)=> {
this.setState({
category: cntry
})
}
setDarkMode = () => {
if (this.state.darkMode === "light") {
this.setState({ darkMode: "dark" })
document.body.style.backgroundColor = "black"
} else {
this.setState({ darkMode: "light" })
document.body.style.backgroundColor = "white"
}
}
render() {
return (
<div>
<Navbar setCategory={this.setCategory} setCountry={this.setCountry} setDarkMode={this.setDarkMode} darkMode={this.state.darkMode} />
<News key={this.state.category} category={this.state.category} country={this.state.country} pageSize={18} darkMode={this.state.darkMode} />
</div>
)
}
}

Related

what is the best way to setParams to navigation before setting the options for Navigation using setOptions?

I wanna know the best way to set the params and options for react native navigation in a class component.
note that the same params are used in options.
when I put all code in the constructor I got params undefined because of timing issue.
and it works. for me in one case when I added option in componentDidMount , I will write some examples in the code below.
1- first case using class component (it's working)
type Props = {
navigation: NavigationProp<any>;
route: RouteProps<{ Example: {title: string} }, 'Example'>
}
export default class Example extends Component <Props> {
constructor(props: Props){
super(props)
this.props.navigation.setParams({ title: 'title' });
}
componentDidMount(){
this.props.navigation.setOptions({ title: this.props.route.params.title })
}
...
}
2 - second case using FC: (not using this example but I think it's also the best way todo for the FC).
export function Example: React.FC = () => {
const navigation = useNavigation();
const route = useRoute();
useLayoutEffect(()=>{
navigation.setParams({ title: 'title' });
navigation.setOptions({ title: route.params.title })
})
...
}
so I hope my question is clear, is that theright way to set Header options with the lates Navigation on React Native?
constructor is the first step in component lifecycle, and you are setting params inside that, which means there is a prop that is going to be updated.
so we need a function that understands every update on a state or received props, and that listener is nothing except "componentDidUpdate(){}" šŸ¤Ÿ:
import {NavigationProp, RouteProp} from '#react-navigation/native';
import React, {Component} from 'react';
import {Text, StyleSheet, View} from 'react-native';
type Props = {
navigation: NavigationProp<any>;
route: RouteProp<{Example: {title: string}}, 'Example'>;
};
export default class Example extends Component<Props> {
constructor(props: Props) {
super(props);
this.props.navigation.setParams({title: 'title'});
}
componentDidUpdate() {
this.props.navigation.setOptions({title: this.props.route.params.title});
}
render() {
return (
<View style={styles.container}>
<Text style={styles.textStyle}>Use component did update :)</Text>
</View>
);
}
}
const styles = StyleSheet.create({
container: {
padding: 30,
},
textStyle: {
color: 'black',
fontSize: 20,
fontWeight: 'bold',
},
});

this.props Not Returning Fetched Data From Parent Component (React)

I am attempting to render playlist information for an Audio Player in React. The data is coming from a fetch call in the parent component (PostContent.js). The data being returned is an array of objects that looks like:
[ {name: ā€˜track nameā€™, artist: ā€˜artist nameā€™, url: ā€™https://blahblah.wav', lrc: ā€˜stringā€™, theme: ā€˜another stringā€™ }, {ā€¦}, {ā€¦}, etc. }
I am not able to return the data in the render() method of the child component (AudioPlayer.js). When I console.log(this.props.audio) in the render(), my terminal prints three responses. The first is an empty array, and the next two are the correct data that I need (an array of objects).
How can I set the props on the ā€˜audioā€™ key in the ā€˜propsā€™ object in the render() method of the AudioPlayer.js component?
I should mention that I am using the react-aplayer library, and I am able to make this work with hard-coded data, as in the example here (https://github.com/MoePlayer/react-aplayer), but I am trying to make a dynamic playlist component for a blog website. Any advice is greatly appreciated.
AudioPlayer.js (Child Component)
import React, { PureComponent, Fragment } from 'react';
import ReactAplayer from '../react-aplayer';
import './AudioPlayer.css';
import sample from '../../src/adrian_trinkhaus.jpeg';
export default class AudioPlayer extends React.Component {
// event binding example
onPlay = () => {
console.log('on play');
};
onPause = () => {
console.log('on pause');
};
// example of access aplayer instance
onInit = ap => {
this.ap = ap;
};
render() {
console.log('props in render of AudioPlayer', this.props.audio)
const props = {
theme: '#F57F17',
lrcType: 3,
audio: this.props.audio
};
return (
<div>
<ReactAplayer
{...props}
onInit={this.onInit}
onPlay={this.onPlay}
onPause={this.onPause}
/>
</div>
);
}
}
PostContent.js (Parent Component)
import React, { Component, useState, Fragment } from 'react';
import { BrowserRouter as Router, Route, Link } from 'react-router-dom';
import AudioPlayer from './AudioPlayer';
export default class PostContent extends Component {
constructor(props) {
super(props);
this.state = {
id: '',
episodeData: [],
audio: []
}
}
async componentDidMount() {
const { id } = this.props.match.params;
const response = await fetch(`http://localhost:5000/episode/${id}/playlist`);
const jsonData = await response.json();
const songs = jsonData;
const audio = Object.keys(songs).map(key => {
return {
name: songs[key].name,
artist: songs[key].artist,
url: songs[key].url,
cover: songs[key].cover,
lrc: songs[key].lrc,
theme: songs[key].theme
}
});
this.setState({ audio })
}
componentDidUpdate(prevProps, prevState) {
if (prevState.audio !== this.state.audio) {
const newAudio = this.state.audio;
this.setState({ audio: newAudio }, () => console.log('new audio', this.state.audio))
}
}
render() {
return (
<Fragment>
<AudioPlayer audio={this.state.audio} />
<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">
<p>{item.post_content1}</p>
<p>{item.post_content2}</p>
<p>{item.post_content3}</p></div>
</div>
))}
<Table data={this.state.data} />
<div className="bottom-link">
<Link id='home-link' to='/' activeClassName='active'>Homepage</Link>
</div>
</Fragment>
)
}
}
i played around with an async scenario with your code on codesandbox
i think the problem is when you're trying to access the payload in ReactAPlayer component when audio it's not loaded yet from the async call. what you need to do is only use "audio" when it's valid like this if (audio.length) {...} or audio && ... some form of check to prevent it from being accessed in the reactAplayer render function.
fyi - you can remove the componentDidUpdate hook, since you have a setState call inside the ...Didmount hook, when setState is called inside ...didMount, the component calls its render() thus trigger a child re-render and its child will do the same..
Actually I think it doesnt work because you set this.props inside a props obejct, so maybe you need to do something like
var that = this
const props = {
audio = that.props.audio
}

component state's var disappears after changing it outside

redux stores language's locale
translator component gets translations from const via key and saves it to their own state. and returns current language's translation in span
im trying to use this library https://www.npmjs.com/package/baffle for fancy effect.
everything works fine with just plain text in translation component. but text disappears, when text depends on state.
not sure if i explained correctly, so there is a little example.
any text - works
{this.state.etc} - doesn't
Translate.jsx
import { TRANSLATIONS } from './../../constants/translations';
class Translate extends React.Component {
constructor(props) {
super(props);
this.state = {
translations: {},
}
}
componentDidMount() {
const translationKeys = this.props.translationKey.split('.');
let translation = TRANSLATIONS;
translationKeys.forEach((i) => {
translation = translation[i]
});
this.setState((state) => ({
translations: translation,
}));
}
render() {
return (
<span ref={this.props.translateRef}>{this.state.translations[this.props.locale]}</span>
)
}
}
Translate.propTypes = {
locale : PropTypes.string.isRequired,
translationKey: PropTypes.string.isRequired,
}
export default Translate;
TranslateContainer.js
const mapStateToProps = state => ({
locale: state.locale,
})
export default connect(mapStateToProps, null, null, {forwardRef: true})(Translate);
and im using this component in react-router-dom custom links
import { Route, Link } from 'react-router-dom';
import classNames from 'classnames';
import baffle from 'baffle';
import css from './RouteLink.module.css';
import Translate from '../Translations/TranslateContainer';
class RouteLink extends React.Component {
constructor(props) {
super(props);
this.baffleRef = React.createRef();
this._onMouseEnter = this._onMouseEnter.bind(this);
}
componentDidMount() {
this.baffle = baffle(this.baffleRef.current);
};
_onMouseEnter() {
this.baffle.start();
}
render() {
return (
<Route
path={this.props.to}
exact={this.props.active}
children={({ match }) => (
<Link
to={this.props.to}
className={classNames({
[css.link]: true,
[css.active]: match,
})}
onMouseEnter={this._onMouseEnter}
>
<Translate
translationKey={this.props.label}
translateRef={this.baffleRef}/>
</Link>
)}
/>
);
}
}
RouteLink.propTypes = {
to : PropTypes.string.isRequired,
label : PropTypes.string.isRequired,
active: PropTypes.bool,
}
export default RouteLink;
translations.js
export const TRANSLATIONS = {
navBar: {
home: {
ru_RU: 'Š“Š»Š°Š²Š½Š°Ń',
en_EN: 'Home',
},
},
}
Is there any way to fix this?
Translations works just fine, switching language works.
Links work.
And if translator returns span without state, even baffle works.
But if translator returns text that depends on state, text just disappears, when baffle starts any function
I don't see the need for the forEach loop in componentDidMount.
The line below will return an array of 2 elements ['navbar','home']
const [firstLevel,secondLevel] = this.props.translationKey.split('.') // array destructuring to get the two values
You could just use the values in the array and locale props to set your translation state.
this.setState({translations: TRANSLATIONS[firstLevel][secondLevel][this.props.locale]})
Then in your render,just use your state
<span ref={this.props.translateRef}>{this.state.translations}</span>
This would solve the issue but as a sidenote,you can do the splitting of props.translationKey and traversing the TRANSLATIONS object even within the render instead of componentDidMount.You don't need the state as you don't seem to have an event handler to setState again.

Passing prop to another component and render on the view

import Form from './Form'
class SideBar extends React.Component {
constructor(props) {
super(props);
this.state = {
item: ''
};
}
render() {
return (
this.props.products.map((x) => {
let boundItemClick = this.onItemClick.bind(this, x);
return <li key={x.id} onClick={boundItemClick}>{x.id}-{x.style_no}-{x.color}</li>
}));
}
onItemClick= function(item, e) {
console.log(item)
}
}
export default SideBar
import SideBar from './SideBar'
class Form extends React.Component{
render(){
return();
}
}
export default Form
**strong text**<div class = first_half><%= react_component("SideBar", {products:
#products}) %></div>
<div class = second_half><%= react_component("Form", {products:
#products}) %></div>
I have a question about how to pass the props to Form component. Right now, the SideBar component list all the link, and everything I click one of the link, I can console.log the information. But I have no idea how to pass it on the other component and render on the view. Thank you
for example : (this is a sidebar componenet)
301-abc
302-efg
303-rgk
When user click 301-abc, it will show coresponding details like id, color, and style.
Likewise SideBar component you have to create constructor a Form component.
And you have to create state with products detail.
class SideBar extends React.Component {
constructor(props) {
super(props);
this.state = {
item: ''
};
}
render() {
return (
this.props.products.map((x) => {
let boundItemClick = this.onItemClick.bind(this, x);
return <li key={x.id} onClick={boundItemClick}>{x.id} - {x.style_no} - {x.color}</li>
}));
}
onItemClick = function(item, e) {
console.log(item)
this.props.selectedData(item);
}
}
export default SideBar
import SideBar from './SideBar'
class Form extends React.Component {
constructor() {
this.state = {
products: [{
id: '302',
style_no: 'abc',
color: 'red'
}, {
id: '303',
style_no: 'abcd',
color: 'black'
}],
selectedData: {}
};
}
getSelectedData(selectedData) {
this.setState({
selectedData: selectedData
});
}
render() {
return (
<SideBar products = {this.state.products} selectedData={this.getSelectedData}>
);
}
}
In above code I have pass method as pops with name selectedData and you can use that in your onItemclick method as I used it and pass your item.
this.props.selectedData(item);
So, you will get that item in your Form component and set your state and you can display it in your Form component and can also pass to another component.
Hope it will work for you..!!
If you got a lot of nested components which need to be aware of each other state/information, your best choice is to choose a state management something like redux,flux or etc. state management mission is to hold data across components and helps you keep uni-direction data-flow in react.
Although, if you don't want to use a state management mechanism for any reason. you may implement it like this (which is ugly):
https://stackblitz.com/edit/react-pkn3cu

react create dyamic menu by settings

I'm have side bar navigation that get a list of json
import {
AppSidebarNav,
} from '#coreui/react';
<AppSidebarNav navConfig={navigation} {...this.props} />
and the data is the list of items
export default {
items: [
{
name: 'Dashboard',
url: '/dashboard',
icon: 'icon-speedometer',
},
{
name: 'Profile',
url: '/profile',
icon: 'icon-speedometer',
},...
],
};
how can I set the list of items before they load to the sidebar ?
there is any way to use componentDidMount()
to update the list ?
how should I approch this task
Put the list of items in its own file, say, nav.js. Then add this line to your imports
import navigation from "./nav"; // or whatever relative path nav.js is in.
You could use this sample code for fetch dynamic data by async, await and axios in AppSidebarNav core-ui
import React, {Component, Suspense} from "react";
import {
AppSidebarNav,
AppSidebar,
AppSidebarFooter,
AppSidebarForm,
AppSidebarHeader,
AppSidebarMinimizer,
} from "#coreui/react";
import axios from "axios";
import navigation from "../../_nav";
class SidebarNav extends Component {
constructor(props) {
super(props);
this.state = {
navData: null,
};
}
async getData() {
let res = await axios.get('REQUEST_URL');
this.setState({ navData: {items : res });
// OR // return await {items : res};
};
componentDidMount() {
if (!this.state.navData) {
(async () => {
try {
await this.getData();
// OR // this.setState({navData: await this.getData()});
} catch (e) {
//...handle the error...
console.log(e);
}
})();
}
}
render() {
return (
<AppSidebar fixed display="lg">
<AppSidebarHeader />
<AppSidebarForm />
<Suspense>
<AppSidebarNav navConfig={(this.state.navData === null) ? navigation : this.state.navData} {...this.props} />
</Suspense>
<AppSidebarFooter />
<AppSidebarMinimizer />
</AppSidebar>
)
}
}
export default SidebarNav;
}
navigation variable could be used for default data or loading...
export default {
items: [
{
name: 'Loading... ',
url: '/'
}
]
}
In the last part call SidebarNav Component in DefaultLayout or anywhere.
Old question, but for anyone wondering how to do this, I moved forward by doing the following:
Parent:
Setup constructor and added currentNavigation prop
Default new prop to default navigation
Change AppSidebarNav to use new prop
Create a function in the parent that takes a nav object, and updates the new currentNavigation prop
In this example, I add my navigation object to a property called index in a new object, this is required for the CoreUI nav it seems, however, you could do this in your child component / when creating the navigation object you will be using to update the menu.
/** Parent **/
import navigation from "../../navigation/_nav"
class DefaultLayout extends Component {
constructor(props) {
super(props)
this.currentNavigation = navigation //Set to default navigation from file
}
functionFromParent = (yourNavObject) => {
// nav contains the navigation object (build this however you do),
// but needs to be added to { items: ... }
let data = {
items: yourNavObject
}
this.currentNavigation = data;
}
render = () => {
return(
// ...More code
<AppSidebarNav
navConfig={this.currentNavigation} //Added function that child fires to prop
{...this.props}
router={router}
/>
// More code...
}
}
}
Child:
Get navigation object however you generate it to update the menu
Pass navigation object through to the functionFromParent using props (this.props.functionFromParent(this.getYourNavigationObject()))
/** Child **/
class ChildComponent extends Component {
// ... More Code
componentDidMount = () => {
let navigation = this.getYourNavigationObject() //Get your navigation object however you do
this.props.functionFromParent(navigation)
}
// More Code ...
}

Categories

Resources