Not able to setstate with respect to array of objects: React JS - javascript

I am trying to map few fields in array of objects, here in this case it is fieldnames and sort order.
I am trying to achieve server side sorting functionality where in the server takes the field name and sort type whenever I click on a field. I just need to map the field names with the sort type(ASCENDING or DESCENDING) .
I have written a sample where I am maintaining a sample array of objects with type. And on click of that column need I need to decide its sorting order
Can someone help here , Just need to achieve the tagging of sort order with the field name
Sandbox: https://codesandbox.io/s/hopeful-wescoff-08x8x
import React from "react";
import ReactDOM from "react-dom";
import { render } from "react-dom";
interface IState {
sorting: any;
}
interface IProps {}
export default class App extends React.Component<IProps, IState> {
constructor(props: any) {
super(props);
this.state = {
sorting: [{ firstName: "" }, { lastName: "" }]
};
}
sortHandler = name => {
const sorting = Object.keys(this.state.sorting).reduce((obj, key) => {
if (key === name) {
obj[key] = this.state.sorting[key] === "ASC" ? "DESC" : "ASC";
} else {
obj[key] = "";
}
return obj;
}, {});
this.setState({ sorting }, () => console.log(this.state.sorting));
};
render() {
return (
<div>
<span onclick={this.sortHandler("firstName")}> FirstName</span>
<span onclick={this.sortHandler("lastName")}> LastName</span>
</div>
);
}
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);

Your click-handlers get executed immediately on render and with the logic you have constructed this will cause the "Maximum update depth exceeded" error.
Pass an anonymous function that will call your sortHandler instead. This will make it so the sortHandler only gets executed when the user clicks the span:
render() {
return (
<div>
<span onclick={() => this.sortHandler("firstName")}> FirstName</span>
<span onclick={() => this.sortHandler("lastName")}> LastName</span>
</div>
);
}
See sandbox for example on how to sort by fieldnames: https://codesandbox.io/s/vigilant-haslett-c2z3f

import React from "react";
import ReactDOM from "react-dom";
//import { render } from "react-dom";
interface IState {
sorting: any;
}
interface IProps {}
export default class App extends React.Component<IProps, IState> {
constructor(props: any) {
super(props);
this.state = {
sorting: [{ firstName: "" }, { lastName: "" }]
};
}
sortHandler = (name => {
const sorting = Object.keys(this.state.sorting).reduce((obj, key) => {
if (key === name) {
obj[key] = this.state.sorting[key] === "ASC" ? "DESC" : "ASC";
} else {
obj[key] = "";
}
return obj;
}, {});
this.setState({ sorting }, () => console.log(this.state.sorting));
});
render() {
return (
<div>
<span onclick={() => this.sortHandler("firstName")}> FirstName</span>
<span onclick={() => this.sortHandler("lastName")}> LastName</span>
</div>
);
}
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
Few Highlighted issue on your code, please do not import the code if you're not using which can consume memory.
Arrow function dedicated to using the function on the scope.
Please check the scope of the variable if the variable required bind with scope
const sorting = Object.keys(this.state.sorting).reduce((obj, key) => {
Please make sure the calling function also requires the scope this to the function. I hope your problem is solved by the initial solution. its just additional notes

Related

Why is React.memo not working with useEffect?

I'm kind of new to react, so what i wanted was that, I have a toggle button to toggle a persons component and I have a cockpit component. But whenever I toggle the persons component, I don't want to always re-render the cockpit component.
So this is my Cockpit.js component file.
import React, { useEffect } from 'react';
import classes from './Cockpit.css';
const cockpit = props => {
useEffect(() => {
console.log('[Cockpit.js] useEffect');
// Http request...
setTimeout(() => {
alert('Saved data to cloud!');
}, 1000);
return () => {
console.log('[Cockpit.js] cleanup work in useEffect');
};
}, []);
useEffect(() => {
console.log('[Cockpit.js] 2nd useEffect');
return () => {
console.log('[Cockpit.js] cleanup work in 2nd useEffect');
};
});
// useEffect();
const assignedClasses = [];
let btnClass = '';
if (props.showPersons) {
btnClass = classes.Red;
}
if (props.personsLength <= 2) {
assignedClasses.push(classes.red); // classes = ['red']
}
if (props.personsLength <= 1) {
assignedClasses.push(classes.bold); // classes = ['red', 'bold']
}
return (
<div className={classes.Cockpit}>
<h1>{props.title}</h1>
<p className={assignedClasses.join(' ')}>This is really working!</p>
<button className={btnClass} onClick={props.clicked}>
Toggle Persons
</button>
</div>
);
};
export default React.memo(cockpit);
And this is my App.js
import React, { Component } from 'react';
import Persons from '../Components/Persons/Persons';
import classes from './App.css';
import Cockpit from '../Components/Cockpit/Cockpit'
class App extends Component {
constructor(props) {
super(props);
console.log("[App.js] constructor");
}
state = {
persons: [{id: "abc", name: "", age: 45},
{id: "azz", name: "", age: 56},
{id: "asq", name: "", age: 62}],
showPersons: false,
showCockpit: true
}
static getDerivedStateFromProps(props, state) {
console.log("[App.js] getDerivedStateFromProps", props)
return state;
}
componentDidMount() {
console.log('[App.js] componentDidMount')
}
shouldComponentUpdate(nextProps, nextState) {
console.log('[App.js] shouldCompoentUpdate');
return true;
}
componentDidUpdate() {
console.log('[App.js] componentDidUpdate')
}
deletePersonHandler = (i) => {
const persons = [...this.state.persons];
persons.splice(i, 1);
this.setState({persons: persons})
}
switchNameHandler = (newName) => {
this.setState({persons: [{name: newName, age: 50}, {name: "Aysha", age: 56}, {name: "Momma", age: 62}]})
}
nameSwitchHandler = (event, id) => {
const personIndex = this.state.persons.findIndex(p => {
return p.id === id;
})
const person = {...this.state.persons[personIndex]}
person.name = event.target.value;
const persons = [...this.state.persons]
persons[personIndex] = person;
this.setState({persons: persons})
}
togglePersonHandler = () => {
let doesChange = this.state.showPersons;
this.setState({showPersons: !doesChange})
}
render() {
console.log("[App.js] render");
let person = null;
if(this.state.showPersons) {
person = (<Persons
persons={this.state.persons}
clicked={this.deletePersonHandler}
changed={this.nameSwitchHandler} />
);
}
return (
<div className={classes.App}>
<button onClick={() => this.setState({showCockpit: false})}>Remove Cockpit</button>
{this.state.showCockpit ? (<Cockpit
title={this.props.appTitle}
showPersons={this.state.showPersons}
personsLength={this.state.persons.length}
clicked={this.togglePersonHandler} />) : null}
{person}
</div>
);
}
}
export default App;
But even when I toggle it, useEffect in cockpit component still console logs in the browser console when its not supposed to. I can't seem to find what I am doing wrong.
As you can see in this image the useEffect component in cockpit still renders in the console......
Browser Console
React.memo will do a shallow equal comparison on the props object by default. That means it will check every top level item in the props for equality and if any of them changed it will re-render.
When you click your persons toggle button it will change showPersons in your App component wich is also a prop that you pass to <Cockpit>. Therefore it will re-render even with React.memo. If it wouldn't re-render it wouldn't correctly update your Button class adding or removing classes.Red because this is dependent on the showPersons prop.
It has nothing to do with your useEffect inside of cockpit which will only get called after it re-renders but doesn't cause it to re-render in the first place.
On the click of Toggle Persons, you are changing the state in App Component.
This results in the re-rendering of the App and Cockpit components.
useEffect(() => {
console.log('[Cockpit.js] 2nd useEffect');
return () => {
console.log('[Cockpit.js] cleanup work in 2nd useEffect');
};
});
The above code will trigger every render as you haven't provided dependency.
To fix this, you need to add a dependency to the above code.
Since showPersons change it detects it as changed props.
You can add an equality function in React.memo that tells react when to consider the memoization stale:
// Will only rerender when someValue changes
export default React.memo(Cockpit, (oldProps, newProps) => oldProps.someValue === newProps.someValue)

Why is my onChange event not working in React?

I am coding a simple search input component for an app that will eventually become larger, but I am at a loss for why the onChange prop associated with it isn't being called. Here I will show my search input component and the app component into which I import it:
import React, { Component } from 'react';
import PropTypes from 'prop-types';
export default class SearchInput extends Component {
static defaultProps = {
onChange: () => Promise.resolve(),
}
static propTypes = {
onChange: PropTypes.func,
value: PropTypes.string,
}
render() {
const { value } = this.props;
return (
<input className="search-input" type='text' onChange={this.handleChange} value={value}/>
)
}
handeChange = (e) => {
const { onChange } = this.props;
onChange(e);
}
}
And then here's my main app component (very simple still, and keep in mind that I have list-rendering functionality, but that isn't where my issue lies). I'm pretty sure the issue lies somewhere in the handleSearchDidChange method that I wrote up and tacked onto the onChange prop for the SearchInput component:
import React, { Component } from 'react';
import Container from './components/container'
import List from './components/list'
import SearchInput from './components/search-input';
// Styles
import './App.css';
export default class App extends Component {
constructor(props){
super(props)
this.state = {
searchValue: undefined,
isSearching: false,
}
// this.handleSearchDidChange = this.handleSearchDidChange.bind(this);
}
render() {
// in the main render, we render the container component (yet to be styled)
// and then call renderList inside of it. We need "this" because this is
// a class-based component, and we need to tell the component that we are
// using the method associated with this class
return (
<div className="App">
<Container>
{this.renderSearchInput()}
{this.renderList()}
</Container>
</div>
);
}
renderSearchInput = () => {
const { searchValue } = this.state;
return (<SearchInput onChange={this.handleSearchDidChange} value={searchValue}/>)
}
renderList = () => {
// return the list component, passing in the fetchData method call as the data prop
// since this prop type is an array and data is an array-type prop, this is
// acceptable
return <List data={this.fetchData()}/>
}
// possibly something wrong with this method?
handleSearchDidChange = (e) => {
const { target } = e;
const { value } = target;
this.setState({
searchValue: value,
isSearching: true,
});
console.log('value: ', value);
console.log('searchValue: ', this.state.searchValue);
console.log('-------------------------')
}
fetchData = () => {
// initialize a list of items
// still wondering why we cannot put the listItems constant and the
// return statement inside of a self-closing setTimeout function in
// order to simulate an API call
const listItems = [
{title: 'Make a transfer'},
{title: 'Wire money'},
{title: 'Set a travel notice'},
{title: 'Pop money'},
{title: 'Edit travel notice'},
{title: 'test money things'},
{title: 'more test money things'},
{title: 'bananas'},
{title: 'apples to eat'},
{title: 'I like CocaCola'},
{title: 'Christmas cookies'},
{title: 'Santa Claus'},
{title: 'iPhones'},
{title: 'Technology is amazing'},
{title: 'Technology'},
{title: 'React is the best'},
];
// return it
return listItems;
}
You have a typo! Missing the "l" in handleChange :)
handleChange = (e) => {
const { onChange } = this.props;
onChange(e);
}
i run your code in sandBox:
https://codesandbox.io/s/onchange-problem-37c4i
there is no issue with your functionality as far as i can see.
but in this case if onChange dose not work for you is because maybe inside of < SearchInput /> component you don't pass the value up to the parent element.
check the sandBox and notice to the SearchInput1 and SearchInput2

Change Event gets mixed up with React and Typescript

I have created a simple app with TypeScript and React.js . I used the create-react-app with the --scripts-version=react-scripts-ts argument and got a working app for a start.
The app now has just 2 components: App.tsx, a stateful component with the list of persons and Person.tsx, a stateless functional component with the person data and the input text for the name.
Here is the code for Person.tsx:
import * as React from "react";
export interface IPersonProps {
id: string;
firstname: string;
change: (event: React.ChangeEvent<HTMLInputElement>) => void;
}
export const Person: React.SFC<IPersonProps> = props => {
return (
<div>
<h1>
Hi {props.firstname}
</h1>
<input type="text" onChange={props.change} value={props.firstname} />
</div>
);
};
And here is the code for App.tsx:
import * as React from "react";
import "./App.css";
import { Person, IPersonProps } from "./Person";
interface IAppState {
persons: Array<IPersonProps>,
showPersons: boolean;
}
class App extends React.Component<{}, IAppState> {
readonly togglePersonsHandler = () => {
const show = this.state.showPersons;
this.setState({showPersons: !show});
}
readonly firstnameChangeHandler = (event:React.ChangeEvent<HTMLInputElement>,
personId: string) => {
const personIndex = this.state.persons.findIndex(person => {
return person.id === personId;
});
const person = {
...this.state.persons[personIndex]
}
person.firstname = event.target.value;
const persons = [...this.state.persons];
persons[personIndex] = person;
this.setState({
persons: persons
} as IAppState);
}
constructor(props: any, state: IAppState) {
super(props, state);
this.state = {
persons: [
{ id: 'a', firstname: "Michael" } as IPersonProps,
{ id: 'b', firstname: "Mel"} as IPersonProps
],
showPersons: false
};
}
public render() {
let persons = null;
if (this.state.showPersons) {
persons = (
<div>
{
this.state.persons.map((person, index) => {
return <Person
id={person.id}
firstname={person.firstname}
key={person.id}
change={this.firstnameChangeHandler.bind(this, event, person.id)} />
})
}
</div>
)
}
return (
<div className="App">
<h1>React</h1>
<button onClick={this.togglePersonsHandler}>Toggle Persons</button>
{persons}
</div>
);
}
}
export default App;
The binding of the firstname to the input field works generally, but not for the first fired change event. The first change event gets an empty value when fired and the firstname will be set to an empty string. All other change events seem to work. As you can see I am using TypeScript and want to be as typesafe as possible...
Maybe a mixed something up with the event binding. Any help is very welcome!
The app looks like that:
React app with event binding
Event binding is indeed the problem here. To make it work I had to change the call of the handler method (in the render method) to:
<Person
id={person.id}
firstname={person.firstname}
key={person.id}
change={this.firstnameChangeHandler.bind(null, person.id)} />
The handler method must change its signature to
readonly firstnameChangeHandler = (personId: string, event:React.ChangeEvent<HTMLInputElement>) => {...}
Documentation on partial binding can be found at:
[1] https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/bind#Partially_applied_functions

Element type is invalid: expected a string & search input not filtering

I have two issues with this code:
First:
I have the following error in the code: Element type is invalid: expected a string (for built-in components) or a class/function (for composite components) but got: undefined.
You likely forgot to export your component from the file it's defined in, or you might have mixed up default and named imports.
Check the render method of App.
How do I fix this? You can find the error here: https://codesandbox.io/s/l5k6w5rqjl
Second, inside <App/>, on line 49 console.log(data.contents); appears undefined. What is the problem here? Is this the reason for <FileTree/> not filtering correctly on search?
import React, { Component } from "react";
import ReactDOM from "react-dom";
import "./styles.css";
import SearchEngine from "./components/search_bar";
import { FileTree } from "./components/file_tree";
import { TextBox } from "./components/text_box";
import { data } from "./components/file_tree";
// Dummy data set
export const root = data[0];
class App extends Component {
constructor(props) {
super(props);
this.state = {
activeNode: "",
root: root
};
this.onChange = this.onChange.bind(this);
}
// Props from <TextBox/> to <App/>
liftStateUp = data => {
this.setState({ activeNode: data });
};
onSubmitSearch = (e, search) => {
e.preventDefault();
let tree = JSON.stringify(root); // always search full data tree
tree = JSON.parse(tree); // JSON.stringify and then JSON.parse are
console.log(tree);
if (!search || search === "") {
// if search is undefined, null or empty, set root to full data tree
this.setState({ root: tree }); // state.root is filtered tree passed to the FileTree component
return;
} else {
this.setState({
root: this.filterTree(tree, search.toLowerCase())
});
}
/* uncoment if you need to filter already filtered tree */
// tree = JSON.stringify(this.state.root);
// tree = JSON.parse(tree);
};
filterTree = (data, search) => {
let children = data.contents;
console.log(data.contents);
if (!children || !children.length) {
if (!data.name.toLowerCase().includes(search)) {
data.remove = true;
}
} else {
for (let i = children.length - 1; i >= 0; i--) {
this.filterTree(children[i], search);
if (children[i].remove) {
children.splice(i, 1);
}
}
if (!children.length) {
data.remove = true;
}
}
return data;
};
onChange(data) {
this.setState({ searchTerm: data });
}
render() {
return (
<div>
<div className="col-md-12">
<SearchEngine
className="form-control"
onChange={this.onChange}
onSubmitSearch={this.onSubmitSearch}
/>
</div>
<div className="col-md-6">
<FileTree
root={this.state.root}
liftStateUp={this.liftStateUp}
searchTerm={this.state.searchTerm}
/>
</div>
<div className="col-md-6">
<TextBox content={this.state.activeNode} />
</div>
</div>
);
}
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
Changes:
1- Add some code in directory.js file:
import React from 'react'
export const Directory = () => <div>Directry</div>;
2- You are not exporting the FileTree from file_tree file:
export class FileTree extends React.Component {....}
3- In search_bar file you are exporting the same component twice, once default and once named export, so remove the export keyword here:
export class SearchEngine
Working Code.

ReactJS looping through set

I've been strugling with this error:
Uncaught TypeError: data.map is not a function
Here's my code:
import React from 'react';
import PropTypes from 'prop-types';
const Foo = ( props ) => {
const data = props.data;
return (
<div>
{
!data ? null : (
data.map((item, index) =>
<a>{item.name}</a>)
)
}
</div>
)
};
export default foo;
What i pass to Foo is a Set<> of these:
public class Bar extends Dto {
public BigDecimal id;
public String name;
}
Any ideas of what might be the case here?
EDIT:
import React, { Component } from 'react';
class AnotherFoo extends Component {
render () {
const data = this.props;
return (
<div>
<Foo data={data.resultSet} />
</div>
);
}
}
I'm guessing your resultSet is null or undefined at some point. One thing you can do to add some robustness and clarity is to add propTypes and defaultProps to your component
import React from 'react';
import PropTypes from 'prop-types';
const Foo = ( props ) => {
const data = props.data;
return (
<div>
{
!data ? null : (
data.map((item, index) =>
<a>{item.name}</a>)
)
}
</div>
);
};
Foo.propTypes = {
data: PropTypes.arrayOf(PropTypes.shape({
name: PropTypes.string
})
};
Foo.defaultProps = {
data: []
};
export default Foo;
This will do a couple things. Give you some warnings when data is the wrong type and or if the items in data are the wrong shape (They should be objects with a name property). Also... it will give you an empty array if data is undefined. This should shed some light on your issue.

Categories