I am trying to generate a list of selector options based on external JSON data. The code here is mostly good, except that part of it is being called before the data is loading resulting in no children being appended. I am sure there is a way to implement this but I'm not sure what that way is for my particular situation.
Here is the code:
class PokedexSelector extends Component {
constructor(props) {
super(props);
this.state = {value: "National", pokedexes: []};
this.generatePokedexList();
this.handleChange = this.handleChange.bind(this);
this.generatePokedexList = this.generatePokedexList.bind(this);
this.pokedexList = this.pokedexList.bind(this);
}
handleChange(event) {
this.setState({value: event.target.value});
}
generatePokedexList() {
const pokedexes = [];
fetch("https://pokeapi.co/api/v2/pokedex/")
.then(response => response.json())
.then(myJson => {
let results = myJson["results"];
results.forEach(function(pokedex) {
let pokedexName = pokedex["name"];
let pokedexLink = "https://pokeapi.co/api/v2/pokedex/" + pokedexName;
let pokedexDisplayName = capitalize(pokedexName.replace('-',' '));
pokedexes.push(
{
name: pokedexName,
displayName: pokedexDisplayName,
link: pokedexLink
}
);
});
this.state.pokedexes = pokedexes;
console.log(this.state.pokedexes)
})
}
pokedexList() {
if (this.state.pokedexes.length > 0) {
console.log("listing")
return (
this.state.pokedexes.map(pokedex => (
<option>{pokedex.displayName}</option>
))
)
}
}
render() {
return (
<select id="pokedex-selector" value={this.state.value} onChange={this.handleChange}>
{this.pokedexList()}
</select>
)
}
}
export default PokedexSelector;
I tried using componentDidMount() as below, but I'm not sure how to specifically target one component for changes in this case (the <select> element).
componentDidMount() {
{this.pokedexList()}
}
Any ideas? Thanks!
You should make your fetch calls before the render method is triggered, ideally in componentDidMount and store the response in the state. The component will re-render only when the state or props changes.
state should be updated via this.setState() method and should not be directly mutated using this.state.
In your case, since you're trying to mutate the state directly using this.state the component will not re-render. You should replace it with this.setState().
Try this code
class PokedexSelector extends Component {
constructor(props) {
super(props);
this.state = {value: "National", pokedexes: []};
this.handleChange = this.handleChange.bind(this);
this.generatePokedexList = this.generatePokedexList.bind(this);
this.pokedexList = this.pokedexList.bind(this);
}
componentDidMount() {
this.generatePokedexList();
}
handleChange(event) {
this.setState({value: event.target.value});
}
generatePokedexList() {
const pokedexes = [];
fetch("https://pokeapi.co/api/v2/pokedex/")
.then(response => response.json())
.then(myJson => {
let results = myJson["results"];
results.forEach(function(pokedex) {
let pokedexName = pokedex["name"];
let pokedexLink = "https://pokeapi.co/api/v2/pokedex/" + pokedexName;
let pokedexDisplayName = capitalize(pokedexName.replace('-',' '));
pokedexes.push(
{
name: pokedexName,
displayName: pokedexDisplayName,
link: pokedexLink
}
);
});
this.setState({pokedexes: pokedexes}); // use setState()
})
}
pokedexList() {
if (this.state.pokedexes.length > 0) {
console.log("listing")
return (
this.state.pokedexes.map(pokedex => (
<option>{pokedex.displayName}</option>
))
)
}
}
render() {
return (
<select id="pokedex-selector" value={this.state.value} onChange={this.handleChange}>
{this.pokedexList()}
</select>
)
}
}
export default PokedexSelector;
It should be this.setState and not this.state.pokedexes = pokedexes. Do not mutate state directly.
this.setState({
pokedexes
})
Related
I was wondering why the child component with the changed value is not getting rendered here.
Isn't it a good idea to have a child handle its own changes or better to have the controller in the parent?
class App extends React.Component {
constructor() {
super();
this.state = {
todos: todosData
};
}
render() {
const todoItems = this.state.todos.map(item => (
<TodoItem key={item.id} item={item} />
));
return <div className="todo-list">{todoItems}</div>;
}
}
This is the Child TodoItem
class TodoItem extends React.Component {
constructor(props) {
super(props);
this.state = {
isComp: {}
};
this.handleChange = this.handleChange.bind(this);
}
handleChange() {
let tempObj = this.state.isComp;
tempObj.completed = !this.state.isComp.completed;
this.setState = { isComp: tempObj };
console.log(this.state.isComp);
}
render() {
this.state.isComp = this.props.item;
console.log(this.state.isComp);
return (
<div className="todo-item">
<input type="checkbox" checked={this.state.isComp.completed} />
<p>{this.props.item.text}</p>
</div>
);
}
}
As you can see the state is changed with handleChange() but this does not fire the render. I am also not too sure if another object can be assigned to an object of the state (let tempObj = thi.state.isComp).
The functionality I am trying to achieve is check and uncheck a box and render accordingly.
What is this?
this.setState = { isComp: tempObj };
I think it should be
this.setState({ isComp: tempObj });
I'm trying to change one value inside a nested state.
I have a state called toDoItems that is filled with data with componentDidMount
The issue is that changing the values work and I can check that with a console.log but when I go to setState and then console.log the values again it doesn't seem like anything has changed?
This is all of the code right now
export default class App extends Component {
constructor(props) {
super(props);
this.state = {
toDoItems: null,
currentView: "AllGroup"
};
}
componentDidMount = () => {
fetch("/data.json")
.then(items => items.json())
.then(data => {
this.setState({
toDoItems: [...data],
});
})
};
changeToDoItemValue = (givenID, givenKey, givenValue) => {
console.log(this.state.toDoItems);
let newToDoItems = [...this.state.toDoItems];
let newToDoItem = { ...newToDoItems[givenID - 1] };
newToDoItem.completedAt = givenValue;
newToDoItems[givenID - 1] = newToDoItem;
console.log(newToDoItems);
this.setState({
toDoItems: {newToDoItems},
})
console.log(this.state.toDoItems);
};
render() {
if (this.state.toDoItems) {
// console.log(this.state.toDoItems[5 - 1]);
return (
<div>
{
this.state.currentView === "AllGroup" ?
<AllGroupView changeToDoItemValue={this.changeToDoItemValue}/> :
<SpecificGroupView />
}
</div>
)
}
return (null)
};
}
class AllGroupView extends Component {
render() {
return (
<div>
<h1 onClick={() => this.props.changeToDoItemValue(1 , "123", "NOW")}>Things To Do</h1>
<ul className="custom-bullet arrow">
</ul>
</div>
)
}
}
So with my console.log I can see this happening
console.log(this.state.toDoItems);
and then with console.log(newToDoItems)
and then again with console.log(this.state.toDoitems) after setState
State update in React is asynchronous, so you should not expect updated values in the next statement itself. Instead you can try something like(logging updated state in setState callback):
this.setState({
toDoItems: {newToDoItems},// also i doubt this statement as well, shouldn't it be like: toDoItems: newToDoItems ?
},()=>{
//callback from state update
console.log(this.state.toDoItems);
})
I've read this post: React setState not Updating Immediately
and realized that setState is async and may require a second arg as a function to deal with the new state.
Now I have a checkbox
class CheckBox extends Component {
constructor() {
super();
this.state = {
isChecked: false,
checkedList: []
};
this.handleChecked = this.handleChecked.bind(this);
}
handleChecked () {
this.setState({isChecked: !this.state.isChecked}, this.props.handler(this.props.txt));
}
render () {
return (
<div>
<input type="checkbox" onChange={this.handleChecked} />
{` ${this.props.txt}`}
</div>
)
}
}
And is being used by another app
class AppList extends Component {
constructor() {
super();
this.state = {
checked: [],
apps: []
};
this.handleChecked = this.handleChecked.bind(this);
this.handleDeleteKey = this.handleDeleteKey.bind(this);
}
handleChecked(client_id) {
if (!this.state.checked.includes(client_id)) {
let new_apps = this.state.apps;
if (new_apps.includes(client_id)) {
new_apps = new_apps.filter(m => {
return (m !== client_id);
});
} else {
new_apps.push(client_id);
}
console.log('new apps', new_apps);
this.setState({apps: new_apps});
// this.setState({checked: [...checked_key, client_id]});
console.log(this.state);
}
}
render () {
const apps = this.props.apps.map((app) =>
<CheckBox key={app.client_id} txt={app.client_id} handler={this.handleChecked}/>
);
return (
<div>
<h4>Client Key List:</h4>
{this.props.apps.length > 0 ? <ul>{apps}</ul> : <p>No Key</p>}
</div>
);
}
}
So every time the checkbox status changes, I update the this.state.apps in AppList
when I console.log new_apps, everything works accordingly, but console.log(this.state) shows that the state is not updated immediately, which is expected. What I need to know is how I can ensure the state is updated when I need to do further actions (like register all these selected strings or something)
setState enables you to make a callback function after you set the state so you can get the real state
this.setState({stateYouWant}, () => console.log(this.state.stateYouWant))
in your case:
this.setState({apps: new_apps}, () => console.log(this.state))
The others have the right answer regarding the setState callback, but I would also suggest making CheckBox stateless and pass isChecked from MyApp as a prop. This way you're only keeping one record of whether the item is checked, and don't need to synchronise between the two.
Actually there shouldn't be two states keeping the same thing. Instead, the checkbox should be stateless, the state should only be kept at the AppList and then passed down:
const CheckBox = ({ text, checked, onChange }) =>
(<span><input type="checkbox" checked={checked} onChange={() => onChange(text)} />{text}</span>);
class AppList extends React.Component {
constructor() {
super();
this.state = {
apps: [
{name: "One", checked: false },
{ name: "Two", checked: false }
],
};
}
onChange(app) {
this.setState(
previous => ({
apps: previous.apps.map(({ name, checked }) => ({ name, checked: checked !== (name === app) })),
}),
() => console.log(this.state)
);
}
render() {
return <div>
{this.state.apps.map(({ name, checked }) => (<CheckBox text={name} checked={checked} onChange={this.onChange.bind(this)} />))}
</div>;
}
}
ReactDOM.render(<AppList />, document.body);
<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.min.js"></script>
So I am trying to use the ref callback pattern explained here: https://facebook.github.io/react/docs/refs-and-the-dom.html, but continue to get undefined in the Parent's componentDidMount method
const Child = ({ createFormHeight, getChildRef }) => (
<CreateForm createFormHeight={createFormHeight}>
// getting reference from here
<div ref={getChildRef}>
// form markup here
</div>
</CreateForm>
);
class Parent extends Component {
constructor(props) {
super(props);
this.state = {
createFormHeight: '',
}
}
componentDidMount() {
const createFormHeight = this.formWrapper.offsetHeight; // returns
error that this.formWrapper is undefined
this.setFormHeight(createFormHeight);
}
getChildRef = (el) => {
// if I console.log(el) it returns the div here
this.formWrapper = el;
}
setFormHeight = (height) => {
this.setState(() => ({ createFormHeight: height }));
};
render() {
const { createIsOpen, createFormHeight } = this.state;
return (
<CreateMember createFormHeight={createFormHeight} getChildRef=
{this.getChildRef} />
);
}
}
I guess the problem is you didn't mount your getChildRef to the context. Try add this.getChildRef = tihs.getChildRef.bind(this) to the constructor of Parent.
The startUpload method inside <Items /> will call the callback function to update the state of the parent component each time it receives a response, This causes <Items /> to be rendered unnecessarily multiple times.
My expected effect is that after the state is updated, only the <Results /> component needs to be re-rendered
class Parent extends React.Component {
constructor(props) {
super(props);
this.getResponseData = this.getResponseData.bind(this);
this.state = {
responseData: [],
}
}
getResponseData(data) {
this.setState({
responseData: this.state.responseData.concat(data),
})
}
render() {
return (
<div>
<Items files={this.props.files} updateData={this.getResponseData}/>
<Results data={this.state.responseData}/>
</div>
)
}
}
class Items extends React.Component {
componentDidMount() {
this.startUpload(this.props.files)
}
startUpload(files) {
const URL = 'http://localhost:3000/upload';
for (let i = 0, len = files.length; i < len; i++) {
const data = new FormData();
data.append('img', files[i]);
fetch(URL, {
method: 'post',
body: data,
})
.then(checkStatus)
.then(parseJSON)
.then(data => {
this.props.updateData(data);
})
}
}
render() {
const filesData = this.getFilesData(this.props.files);
let imageItems = filesData.map((current) => {
return (
<div>
<img src={current.objectURL} alt="preview"/>
</div>
)
});
return <div>{imageItems}</div>;
}
}
function Results(props) {
const responseData = props.data;
let result = [];
if (responseData.length) {
result = responseData.map(current => {
return <p>{current}</p>
});
return <div>{result}</div>
}
}
https://facebook.github.io/react/docs/react-component.html#shouldcomponentupdate You can use shouldComponentUpdate to inform your component whether or not should re-render or not based on a change in state/props. Using this knowledge, you can implement the logic you need in order to render the Items/Results component only when needed.
Hope that helps!