Variable not available outside of a function in a react component - javascript

The state gets passed in via props (selectedCategory). Using axios to get an array... it comes back, I can console.log it within the function. I'd like to assign it to a variable and then outside of the function I'd like to map over it. But it is unavailable (undefined)
import React from 'react';
import SearchCategoryMembers from '../libs/category-members';
import MemberItem from './member_item';
const Members = ({ selectedCategory }) => {
if (!selectedCategory) {
return <div className="none" />;
}
SearchCategoryMembers({ categoryTitle: selectedCategory.title }, members => {
console.log(members);
});
// members not available to map over outside of function
return <ul className="member-list" />;
};
export default Members;

What you're trying to do is a common paradigm in React. You're asking for data from some outside source, and then once it's retrieved my guess is you want to render it in this component but you're trying to use a stateless component to do it. You need a way of keeping track of when the callback in SearchCategoryMembers has completed. You have to use state.
import React, { Component } from 'react';
import SearchCategoryMembers from '../libs/category-members';
import MemberItem from './member_item';
class Members extends Component {
constructor(props) {
super(props);
// We'll put our members here once we get them back
this.state = { };
}
// This is generally where you want to make api calls in the react lifecycle
componentDidMount() {
const selectedCategory = this.props.selectedCategory;
if (!selectedCategory) return;
SearchCategoryMembers(
{ categoryTitle: selectedCategory.title },
members => {
// now we have our members data, set it on state
this.setState({ members: members });
// or fancy es6 shorthand: this.setState({ members });
}
);
}
render() {
// my guess is you want to render something about the members here
if (!this.state.members) return null;
return (
<ul>
{this.state.members.map((member) => <li>{member.name}</li>)}
</ul>
);
}
}
export default Members;

The issue is the SearchCategoryMembers is an asynchronous function so we need to wait for the data to be returned before we can use the members data.
To do this we can fetch the data from the asynchronous API request and update the state of the parent component. We'll initially set the members state to an empty array. When the component mounts we'll request the data and when it's returned we'll update the members state, which is then passed to the <Members /> component which then renders the members array passed to it.
Here's a simplified working example:
function SearchCategoryMembers() {
return axios.get('https://jsonplaceholder.typicode.com/posts');
}
class MembersWrapper extends React.Component {
constructor(props) {
super(props);
this.state = {
members: []
}
}
componentDidMount = () => {
SearchCategoryMembers().then(data => {
this.setState({
members: data.data
})
});
}
render() {
return (
<Members members={this.state.members} />
)
}
}
function Members(props) {
return (
<ul>
{props.members.map((member, id) => <li key={id}>{member.title}</li>)}
</ul>
)
}
ReactDOM.render(<MembersWrapper />, document.getElementById("app"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/axios/0.17.0/axios.js"></script>
<div id="app"></div>

try Members.props.selectedCategory

Related

How to pass data in two way data binding in class base component in ReactJS?

My problem that is in a child component, I have a state that updated via post method and I must show this state in my parent component both these component are the class base component
The ReactJS is a library with One directional data binding. So that is not possible to pass data like Angular or VueJS. you should pass a handler function to the child-component and then after the Axios answer update the local and also the parent component.
And there is a little hint here, there is no different for your situation between class components and functional components. pay attention to the sample code:
class ParentComponent extends React.Component {
state = {
data: undefined,
};
handleGetData = data => {
this.setState({
data,
});
};
render() {
return (
<ChildComponent onGetData={this.handleGetData} />
);
}
}
And now inside the ChildComponent you can access to the handler function:
class ChildComponent extends React.Component {
componentDidMound() {
const { onGetData } = this.props;
Axios
.get('someSampleUrl')
.then( (data) => {
onGetData(data); // running this update your parent state
});
}
render() {
return (
<div />
);
}
}
React's practice is one-directional. For best practice, you must 'Raise' the states into the parent component.
But if you were looking specifically to pass data up, you need to pass a callback down as a prop. use that callback function to pass the data up:
Parent:
<Parent>
<Child callback={console.log} />
</Parent>
Child:
const Child = (props) => {
const { callback } = props;
const postData = (...) => {
...
callback(result);
}
...
}

React not loading state properly on mount during axios call

Basically I am making a basic react App that is grabbing some data from a DB and I am stuck on the basic setup.
My intention is to have my state contain the response from my server querying my databse.
My response is 100% working and sending the data back as expected from the axios call, however the state is never getting update.
EDIT : I am attempting to pass the movies down the chain to a Component called MovieList, I have provided the code for that as well.
App.jsx
import React from 'react';
import axios from 'axios';
import MovieList from './MovieList.jsx';
class App extends React.Component {
constructor(props) {
super(props);
this.state = {allMovies: []};
}
componentDidMount() {
var that = this;
axios.get('http://localhost:3000/api/movies')
.then( res => {
that.setState({allMovies: res.data});
})
.catch( err => {
console.log(`Err # [ App.jsx - componentDidMount ] ::: ${err}`);
});
}
render() {
return (
<div>
<h1>Hello World!</h1>
<MovieList movies={this.state.allMovies} />
</div>
)
}
}
MovieList.jsx
import React from 'react';
class MovieList extends React.Component {
constructor(props) {
super(props);
this.state = {};
console.log(this.props); //EMPTY OBJECT MOVIES DIDN'T GET INTO PROPS
}
render() {
return (
<div>
<h1>Test</h1>
</div>
)
}
}
export default MovieList;
NOTE : I also logged the props on mount and attempted to render them and they were empty.
Basically if I try to pass down this.state.allMovies or console.log it, its always just the initial empty array.
Maybe I don't understand how async setting the state can be done? I took a similar approach on my last school project and it seemed to work fine.
You don't await the axios promise to resolve, so you simply are logging what the state is when the component mounts. Use one of the following to log updated react state.
Use componentDidUpdate to log the updated state.
componentDidMount() {
console.log(this.state);
}
Use the setState callback function to log the state
componentDidMount() {
var that = this;
axios.get('http://localhost:3000/api/movies')
.then( res => {
console.log(res.data);
that.setState(
{ allMovies: [res.data] },
() => console.log(this.state), // <-- setState callback function
);
})
.catch( err => {
console.log(`Err # [ App.jsx - componentDidMount ] ::: ${err}`);
});
}
You'll never see it in your constructor, because when your component is instantiated, it's done so with an empty array.
You will see it if you do a console.log(this.props) in componentDidUpdate or render however.
This is because when App is mounted, your component passes a movies prop of [] to MovieList. After the movies return from the server (and you update the state of App), App will render again and pass the array returned from the server, causing your MovieList component to render again. It's constructor won't be called, because it's already instantiated, but MovieList will call componentDidUpdate and render again.
class MovieList extends React.Component {
constructor(props) {
super(props);
this.state = {};
}
render() {
console.log(this.props); // you'll see this get logged twice - once with an empty array for movies and once with the movies returned from the server.
return (
<div>
<h1>Test</h1>
</div>
)
}
}

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
}

setState/use State in external function react

Considering this pseudocode:
component.js
...
import {someFunc} from "./common_functions.js"
export default class MyComp extends Component {
constructor(props) {
super(props);
this.someFunc = someFunc.bind(this);
this.state = {...};
}
_anotherFunc = () = > {
....
this.someFunc();
}
render() {
...
}
}
common_functions.js
export function someFunc() {
if(this.state.whatever) {...}
this.setState{...}
}
How would I bind the function someFunc() to the context of the Component? I use it in various Components, so it makes sense to collect them in one file. Right now, I get the error "Cannot read whatever of undefined". The context of this is unknown...
You can't setState outside of the component because it is component's local state. If you need to update state which is shared, create a store (redux store).
In your case, you can define someFunction at one place and pass it the specific state variable(s) or entire state. After you are done in someFunction, return the modified state and update it back in your component using setState.
export function someFunc(state) {
if(state.whatever) {...}
const newState = { ...state, newValue: whateverValue }
return newState
}
_anotherFunc = () = > {
....
const newState = this.someFunc(this.state);
this.setState({newValue: newState});
}
it's not a React practice and it may cause lot of problems/bugs, but js allows to do it:
Module A:
export function your_external_func(thisObj, name, val) {
thisObj.setSate((prevState) => { // prevState - previous state
// do something with prevState ...
const newState = { // new state object
someData: `This is updated data ${ val }`,
[name]: val,
};
return newState
});
}
Then use it in your react-app module:
import { your_external_func } from '.../your_file_with_functions';
class YourReactComponent extends React.Component {
constructor(props, context) {
super(props, context);
this.state={
someName: '',
someData: '',
};
}
handleChange = (e) => {
const { target } = event;
const { name } = target;
const value = target.type === 'checkbox' ? target.checked : target.value;
your_external_func(this, name, value);
}
render() {
return (<span>
{ this.state.someData }
<br />
<input
name='someName'
value={ this.state.someName }
onChange={ this.handleChange }
/>
</span>);
}
}
It's a stupid example :) just to show you how you can do it
The best would obviously to use some kind of external library that manages this. As others have suggested, Redux and MobX are good for this. Using a high-order component to wrap all your other components is also an option.
However, here's an alternative solution to the ones above:
You could use a standard javascript class (not a React component) and pass in this to the function that you are calling from that class.
It's rather simple. I've created a simple example below where the state is changed from a function of another class; take a look:
class MyApp extends React.Component {
constructor() {
super();
this.state = {number: 1};
}
double = () => {
Global.myFunc(this);
}
render() {
return (
<div>
<p>{this.state.number}</p>
<button onClick={this.double}>Double up!</button>
</div>
);
}
}
class Global {
static myFunc = (t) => {
t.setState({number: t.state.number*2});
}
}
ReactDOM.render(<MyApp />, document.getElementById("app"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="app"><div>
There is a functional form of setState that can even be used outside of a component.
This is possible since the signature of setState is:
* #param {object|function} partialState Next partial state or function to
* produce next partial state to be merged with current state.
* #param {?function} callback Called after state is updated.
See Dan's tweet: https://twitter.com/dan_abramov/status/824308413559668744
This all depends on what you are trying to achieve. At first glance I can see 2 options for you. One create a child component and two: use redux as redux offers a singular state between all of your child components.
First option:
export default class parentClass extends Component {
state = {
param1: "hello".
};
render() {
return (
<Child param1={this.state.param1}/>
);
}
}
class Child extends Component {
render() {
console.log(this.props.param1);
return (
<h1>{this.props.param1}</h1>
);
}
}
Now the above child component will have the props.param1 defined from the props passed from it's parent render function.
The above would work but I can see you're trying to establish a 'common' set of functions. Option 2 sort of provides a way of doing that by creating a singular state for your app/project.
If you've haven't used redux before it's pretty simple to use once you've got the hang of it. I'll skip out the setup for now http://redux.js.org/docs/basics/UsageWithReact.html.
Make a reducer like so:
import * as config from './config';//I like to make a config file so it's easier to dispatch my actions etc
//const config.state = {param1: null}
//const config.SOME_FUNC = "test/SOME_FUNC";
export default function reducer(state = config.state, action = {}) {
switch(action.type) {
case config.SOME_FUNC:
return Object.assign({}, state, {
param1: action.param1,
});
break;
default:
return state;
}
}
}
Add that to your reducers for your store.
Wrap all your components in the Provider.
ReactDOM.render(
<Provider store={store} key="provider">
<App>
</Provider>,
element
);
Now you'll be able to use redux connect on all of the child components of the provider!
Like so:
import React, {Component} from 'react';
import {connect} from 'react-redux';
#connect(
state => (state),
dispatch => ({
someFunc: (param1) => dispatch({type: config.SOME_FUNC, param1: param1}),
})
)
export default class Child extends Component {
eventFunction = (event) => {
//if you wanted to update the store with a value from an input
this.props.someFunc(event.target.value);
}
render() {
return (
<h1>{this.props.test.param1}</h1>
);
}
}
When you get used to redux check this out https://github.com/redux-saga/redux-saga. This is your end goal! Sagas are great! If you get stuck let me know!
Parent component example where you define your callback and manage a global state :
export default class Parent extends Component {
constructor() {
super();
this.state = {
applyGlobalCss: false,
};
}
toggleCss() {
this.setState({ applyGlobalCss: !this.state.applyGlobalCss });
}
render() {
return (
<Child css={this.state.applyGlobalCss} onToggle={this.toggleCss} />
);
}
}
and then in child component you can use the props and callback like :
export default class Child extends Component {
render() {
console.log(this.props.css);
return (
<div onClick={this.props.onToggle}>
</div>
);
}
}
Child.propTypes = {
onToggle: PropTypes.func,
css: PropTypes.bool,
};
Well for your example I can see you can do this in a simpler way rather than passing anything.
Since you want to update the value of the state you can just return it from the function itself.
Just make the function you are using in your component async and wait for the function to return a value and set the state to that value.
import React from "react"
class MyApp extends React.Component {
constructor() {
super();
this.state = {number: 1};
}
theOnlyFunction = async() => {
const value = await someFunctionFromFile( // Pass Parameters );
if( value !== false ) // Just for your understanding I am writing this way
{
this.setState({ number: value })
}
}
render() {
return (
<div>
<p>{this.state.number}</p>
<button onClick={this.double}>Double up!</button>
</div>
);
}
}
And in SomeOtherFile.js
function someFunctionFromFile ( // catch params) {
if( //nah don't wanna do anything ) return false;
// and the blahh blahh algorithm
}
you should use react Context
Context lets us pass a value deep into the component tree without explicitly threading it through every component.
here is a use case from react docs : create a context for the current theme (with "light" as the default).
const ThemeContext = React.createContext('light');
class App extends React.Component {
render() {
// Use a Provider to pass the current theme to the tree below.
// Any component can read it, no matter how deep it is.
// In this example, we're passing "dark" as the current value.
return (
<ThemeContext.Provider value="dark">
<Toolbar />
</ThemeContext.Provider>
);
}
}
// A component in the middle doesn't have to
// pass the theme down explicitly anymore.
function Toolbar() {
return (
<div>
<ThemedButton />
</div>
);
}
class ThemedButton extends React.Component {
// Assign a contextType to read the current theme context.
// React will find the closest theme Provider above and use its value.
// In this example, the current theme is "dark".
static contextType = ThemeContext;
render() {
return <Button theme={this.context} />;
}
}
resource: https://reactjs.org/docs/context.html

Reactjs, parent component, state and props

I m actually learning reactjs and I m actually developping a little TODO list, wrapped inside of a "parent component" called TODO.
Inside of this parent, I want to get the current state of the TODO from the concerned store, and then pass this state to child component as property.
The problem is that I dont know where to initialize my parent state values.
In fact, I m using ES6 syntax, and so, I dont have getInitialState() function. It's written in the documentation that I should use component constructor to initialize these state values.
The fact is that if I want to initialize the state inside of my constructor, the this.context (Fluxible Context) is undefined actually.
I decided to move the initialization inside of componentDidMount, but it seems to be an anti pattern, and I need another solution. Can you help me ?
Here's my actual code :
import React from 'react';
import TodoTable from './TodoTable';
import ListStore from '../stores/ListStore';
class Todo extends React.Component {
constructor(props){
super(props);
this.state = {listItem:[]};
this._onStoreChange = this._onStoreChange.bind(this);
}
static contextTypes = {
executeAction: React.PropTypes.func.isRequired,
getStore: React.PropTypes.func.isRequired
};
componentDidMount() {
this.setState(this.getStoreState()); // this is what I need to move inside of the constructor
this.context.getStore(ListStore).addChangeListener(this._onStoreChange);
}
componentWillUnmount() {
this.context.getStore(ListStore).removeChangeListener(this._onStoreChange);
}
_onStoreChange () {
this.setState(this.getStoreState());
}
getStoreState() {
return {
listItem: this.context.getStore(ListStore).getItems() // gives undefined
}
}
add(e){
this.context.executeAction(function (actionContext, payload, done) {
actionContext.dispatch('ADD_ITEM', {name:'toto', key:new Date().getTime()});
});
}
render() {
return (
<div>
<button className='waves-effect waves-light btn' onClick={this.add.bind(this)}>Add</button>
<TodoTable listItems={this.state.listItem}></TodoTable>
</div>
);
}
}
export default Todo;
As a Fluxible user you should benefit from Fluxible addons:
connectToStores.
The following example will listen to changes in FooStore and BarStore and pass foo and bar as props to the Component when it is instantiated.
class Component extends React.Component {
render() {
return (
<ul>
<li>{this.props.foo}</li>
<li>{this.props.bar}</li>
</ul>
);
}
}
Component = connectToStores(Component, [FooStore, BarStore], (context, props) => ({
foo: context.getStore(FooStore).getFoo(),
bar: context.getStore(BarStore).getBar()
}));
export default Component;
Look into fluxible example for more details. Code exсerpt:
var connectToStores = require('fluxible-addons-react/connectToStores');
var TodoStore = require('../stores/TodoStore');
...
TodoApp = connectToStores(TodoApp, [TodoStore], function (context, props) {
return {
items: context.getStore(TodoStore).getAll()
};
});
As a result you wouldn't need to call setState, all store data will be in component's props.

Categories

Resources