Why can't I add any value to the array in state? - javascript

I have a lot of hits, which I want to add to an array once a hit is pressed. However, as far as I observed, the array looked like it got the name of the hit, which is the value. The value was gone in like half second.
I have tried the methods like building constructor, and doing things like
onClick={e => this.handleSelect(e)}
value={hit.name}
onClick={this.handleSelect.bind(this)}
value={hit.name}
onClick={this.handleSelect.bind(this)}
defaultValue={hit.name}
and so on
export default class Tagsearch extends Component {
constructor(props) {
super(props);
this.state = {
dropDownOpen:false,
text:"",
tags:[]
};
this.handleRemoveItem = this.handleRemoveItem.bind(this);
this.handleSelect = this.handleSelect.bind(this);
this.handleTextChange = this.handleTextChange.bind(this);
}
handleSelect = (e) => {
this.setState(
{ tags:[...this.state.tags, e.target.value]
});
}
render() {
const HitComponent = ({ hit }) => {
return (
<div className="infos">
<button
className="d-inline-flex p-2"
onClick={e => this.handleSelect(e)}
value={hit.name}
>
<Highlight attribute="name" hit={hit} />
</button>
</div>
);
}
const MyHits = connectHits(({ hits }) => {
const hs = hits.map(hit => <HitComponent key={hit.objectID} hit={hit}/>);
return <div id="hits">{hs}</div>;
})
return (
<InstantSearch
appId="JZR96HCCHL"
apiKey="b6fb26478563473aa77c0930824eb913"
indexName="tags"
>
<CustomSearchBox />
{result}
</InstantSearch>
)
}
}
Basically, what I want is to pass the name of the hit component to handleSelect method once the corresponding button is pressed.

You can simply pass the hit.name value into the arrow function.
Full working code example (simple paste into codesandbox.io):
import React from "react";
import ReactDOM from "react-dom";
const HitComponent = ({ hit, handleSelect }) => {
return <button onClick={() => handleSelect(hit)}>{hit.name}</button>;
};
class Tagsearch extends React.Component {
constructor(props) {
super(props);
this.state = {
tags: []
};
}
handleSelect = value => {
this.setState(prevState => {
return { tags: [...prevState.tags, value] };
});
};
render() {
const hitList = this.props.hitList;
return hitList.map(hit => (
<HitComponent key={hit.id} hit={hit} handleSelect={this.handleSelect} />
));
}
}
function App() {
return (
<div className="App">
<Tagsearch
hitList={[
{ id: 1, name: "First" },
{ id: 2, name: "Second" },
{ id: 3, name: "Third" }
]}
/>
</div>
);
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
additionally:
note the use of prevState! This is a best practice when modifying state. You can google as to why!
you should define the HitComponent component outside of the render method. it doesn't need to be redefined each time the component is rendered!

Related

Get consolidated data from all the child components in the form of an object inside a parent component : React JS

I am implementing a setting page for an application. For each setting I have implemented a slider that has enabled(green) or disabled(red) state. But parent's settings is read only and is calculated based on the values of its children.
Parent's setting is derived as follows: If all children are red, parent stays red ; If all are green parent stays green; If at-least one of child is green then parent stays grey(Pending).
These settings are grouped something like this:
Parent Feature 1 : (read-only-toggle)
Setting 1 (Toggle)
Setting 2 (Toggle)
Parent Feature 2: (read-only-toggle)
Setting 1 (Toggle)
Setting 2 (Toggle)
And in the end there is also a button, that gives me a consolidated values of all parent and children. But so far I was able to do only with one parent and 2 children.
Can someone help with an approach of getting consolidated values of all the settings in one place(Like a super parent component where all these settings are configured).
For this , I am using react-multi-toggle for this toggle switch.
Help would be really appreciated.
Code Sandbox: https://codesandbox.io/s/react-multi-toggle-solution-perfect-v9bi5
App
import React from "react";
import ChildSwitch from "./ChildSwitch";
import ParentSwitch from "./ParentSwitch";
export default class App extends React.Component {
constructor(props) {
super(props);
this.state = {
parentVal: "disabled",
switch1Val: "enabled",
switch2Val: "disabled"
};
}
componentDidMount() {
this.setParentSwitchValue();
}
onGetChildSwitchValues = () => {
console.log(this.state);
};
setChildSwitchValue = (whichSwitch, selected) => {
this.setState(
prevState => ({ ...prevState, [whichSwitch]: selected }),
this.setParentSwitchValue
);
};
setParentSwitchValue = () => {
const { switch1Val, switch2Val } = this.state;
const switchStates = [switch1Val, switch2Val];
let parent = "pending";
if (switchStates.every(val => val === "enabled")) {
parent = "enabled";
}
if (switchStates.every(val => val === "disabled")) {
parent = "disabled";
}
this.setState(prevState => ({ ...prevState, parentVal: parent }));
};
render() {
const { parentVal, switch1Val, switch2Val } = this.state;
return (
<>
<div className="boxed">
Parent Setting 1 :{" "}
<ParentSwitch
parentSwitch={parentVal}
onSelect={this.setParentSwitchValue}
/>
Setting 1:
<ChildSwitch
switchName={"switch1Val"}
selected={switch1Val}
onSelect={this.setChildSwitchValue}
/>
Setting 2:
<ChildSwitch
switchName={"switch2Val"}
selected={switch2Val}
onSelect={this.setChildSwitchValue}
/>
</div>
<button onClick={this.onGetChildSwitchValues}>Get All Values</button>
</>
);
}
}
ChildSetting
import MultiToggle from "react-multi-toggle";
import React from "react";
export default class ChildSwitch extends React.Component {
constructor(props) {
super(props);
this.state = {
options: [
{
displayName: "Disabled",
value: "disabled"
},
{
displayName: "Enabled",
value: "enabled"
}
]
};
}
onSelectOption = selected => {
this.props.onSelect(this.props.switchName, selected);
};
render() {
const { options } = this.state;
const { selected } = this.props;
return (
<MultiToggle
options={options}
selectedOption={selected}
onSelectOption={this.onSelectOption}
/>
);
}
}
Parent Setting
import MultiToggle from "react-multi-toggle";
import React from "react";
import "react-multi-toggle/style.css";
export default class ParentSwitch extends React.Component {
constructor(props) {
super(props);
this.state = {
options: [
{
displayName: "Disabled",
value: "disabled"
},
{
displayName: "Pending",
value: "pending"
},
{
displayName: "Enabled",
value: "enabled"
}
]
};
}
render() {
const { options } = this.state;
return (
<MultiToggle
options={options}
selectedOption={this.props.parentSwitch}
onSelectOption={() => {}}
/>
);
}
}
I will suggest that you group your child and parent under one component. Let say we name it Settings. Then, we create another component that will render a list of Settings and a button. This last component will hold the values of all Settings. Finally, each time the value of a Setting Component Change, we update the list. Checkout a sample working app here.
App Component
export default class App extends PureComponent {
state = {};
onSettingChange = (settingId, setting) => {
this.setState(prevState => ({
...prevState,
[settingId]: setting
}));
};
onGetSettingValues = () => {
console.log(this.state);
};
render() {
return (
<Fragment>
<Setting id="setting1" onChange={this.onSettingChange} />
<Setting id="setting2" onChange={this.onSettingChange} />
<button onClick={this.onGetSettingValues}>Get All Values</button>
</Fragment>
);
}
}
Setting Component
import React, { PureComponent, Fragment } from "react";
import ChildSwitch from "./ChildSwitch";
import ParentSwitch from "./ParentSwitch";
export default class Setting extends PureComponent {
state = {
parentVal: "disabled",
switch1Val: "enabled",
switch2Val: "disabled"
};
componentDidMount() {
this.setParentSwitchValue();
}
setChildSwitchValue = (whichSwitch, selected) => {
this.setState(
prevState => ({ ...prevState, [whichSwitch]: selected }),
this.setParentSwitchValue
);
};
handleChange = () => {
const { id, onChange } = this.props;
onChange(id, this.state);
};
setParentSwitchValue = () => {
const { switch1Val, switch2Val } = this.state;
const switchStates = [switch1Val, switch2Val];
let parent = "pending";
if (switchStates.every(val => val === "enabled")) {
parent = "enabled";
}
if (switchStates.every(val => val === "disabled")) {
parent = "disabled";
}
this.setState(
prevState => ({ ...prevState, parentVal: parent }),
this.handleChange
);
};
render() {
const { parentVal, switch1Val, switch2Val } = this.state;
return (
<Fragment>
<div className="boxed">
Parent Setting 1
<ParentSwitch
parentSwitch={parentVal}
onSelect={this.setParentSwitchValue}
/>
Setting 1:
<ChildSwitch
switchName={"switch1Val"}
selected={switch1Val}
onSelect={this.setChildSwitchValue}
/>
Setting 2:
<ChildSwitch
switchName={"switch2Val"}
selected={switch2Val}
onSelect={this.setChildSwitchValue}
/>
</div>
</Fragment>
);
}
}
Put all your states into a single context hook.
const SettingsContext = createContext({state1, state2/* all your states in here*/);
You'll then wrap the whole thing into this context as such:
<SettingsContext.Provider>
<App/>
</SettingsContext.Provider>
Now you can access the state in any of the children, parents etc. I suggest however not storing things like "disabled", "enabled" as strings, but rather store states as { enabled: true, pending: false}

Child Component not updating after state changes

I am learning react, and I am making a simple ToDoApp. I set some todo data from a JSON file in the state of my App Component and use the values to populate a Child component. I wrote a method to be called each time the onChange event is fired on a checkbox element and flip the checkbox by updating the state. Thing is this code worked perfectly fine before, but it's not anymore. The state gets updated accordingly when I change the checkbox, but it doesn't update in the child element, I'd like to know why. Here's my code
App.js
import React from "react";
import TodoItem from "./TodoItem";
import toDoData from "./toDosData";
class App extends React.Component {
constructor() {
super();
this.state = {
toDoData: toDoData
};
this.handleOnChange = this.handleOnChange.bind(this);
}
handleOnChange(key)
{
this.setState(prevState => {
let newState = prevState.toDoData.map(currentData => {
if(currentData.id === key)
currentData.completed = !currentData.completed;
return currentData;
});
return {toDoData: newState};
});
}
render() {
let toDoComponents = this.state.toDoData.map(toDoDatum =>
<TodoItem key={toDoDatum.id} details={{
key: toDoDatum.id,
text: toDoDatum.text,
completed: toDoDatum.completed,
onChange: this.handleOnChange
}} />);
return (
<div>
{toDoComponents}
</div>
);
}
}
export default App;
TodoItem.js
import React from "react";
class TodoItem extends React.Component {
properties = this.props.details;
render() {
return (
<div>
<input type="checkbox" checked={this.properties.completed}
onChange={() => this.properties.onChange(this.properties.key)}
/>
<span>{this.properties.text}</span>
</div>
)
}
}
export default TodoItem;
Thanks in advance.
Why do you need to assign your details prop to properties in your class? If you do that properties does not reflect the prop changes and your child component can't see the updates. Just use the props as it is:
render() {
const { details } = this.props;
return (
<div>
<input
type="checkbox"
checked={details.completed}
onChange={() => details.onChange(details.key)}
/>
<span>{details.text}</span>
</div>
);
}
}
Also, since you don't use any state or lifecycle method in TodoItem component, it can be a functional component as well.
const TodoItem = ({ details }) => (
<div>
<input
type="checkbox"
checked={details.completed}
onChange={() => details.onChange(details.key)}
/>
<span>{details.text}</span>
</div>
);
One more thing, why don't you pass the todo itself to TodoItem directly?
<TodoItem
key={toDoDatum.id}
todo={toDoDatum}
onChange={this.handleOnChange}
/>
and
const TodoItem = ({ todo, onChange }) => (
<div>
<input
type="checkbox"
checked={todo.completed}
onChange={() => onChange(todo.id)}
/>
<span>{todo.text}</span>
</div>
);
Isn't this more readable?
Update after comment
const toDoData = [
{ id: 1, text: "foo", completed: false },
{ id: 2, text: "bar", completed: false },
{ id: 3, text: "baz", completed: false }
];
const TodoItem = ({ todo, onChange }) => (
<div>
<input
type="checkbox"
checked={todo.completed}
onChange={() => onChange(todo.id)}
/>
<span>{todo.text}</span>
</div>
);
class App extends React.Component {
constructor() {
super();
this.state = {
toDoData: toDoData
};
this.handleOnChange = this.handleOnChange.bind(this);
}
handleOnChange(key) {
this.setState(prevState => {
let newState = prevState.toDoData.map(currentData => {
if (currentData.id === key)
currentData.completed = !currentData.completed;
return currentData;
});
return { toDoData: newState };
});
}
render() {
let toDoComponents = this.state.toDoData.map(toDoDatum => (
<TodoItem
key={toDoDatum.id}
todo={toDoDatum}
onChange={this.handleOnChange}
/>
));
return <div>{toDoComponents}</div>;
}
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<div id="root" />

"Cannot read property 'map' of undefined" within React, what's wrong here?

Trying to get my head around props so forgive me if its a silly mistake. I am trying to pass all of my data into one variable and pass that out into props (using {item.text} and {item.key}), however, my ".map" isn't picking up anything and there's a bunch of errors, what's wrong with my code?
The problem lays specifically here in this block of code
createList(list) {
return <li>{list.text}</li>
}
render() {
var entries = this.state.list
var finalEntries = entries.props.map(this.createList)
Here is the code in full
import React from "react";
import "./App.css";
import { isTemplateElement } from "#babel/types";
class TodoListt extends React.Component {
state = {};
constructor(props) {
super(props);
this.state = {
userInput: "",
list: [],
};
}
changeUserInput(input) {
this.setState({
userInput: input
})
}
addToList(input) {
let listArray = this.state.list;
listArray.push(input);
var newItem = {
text: listArray,
key: Date.now()
};
this.setState(prevState => {
return {
list: prevState.list.concat(newItem)
};
});
this.setState({
list: listArray
})
}
createList(list) {
return <li>{list.text}</li>
}
render() {
var entries = this.state.list
var finalEntries = entries.props.map(this.createList)
return (
<div className="to-do-list-main">
<input
onChange={(e) => this.changeUserInput(e.target.value)}
value={this.state.userInput}
type="text"
/>
<button onClick={() => this.addToList(this.state.userInput)}>Press me</button>
<ul>
{this.testingSetup()}
</ul>
</div>
);
}
}
export default TodoListt;
You can use the spread operator to add to an existing array. Simply add a new object to the array in the state, and then clear the user input, ready for another item. Based on your code, here's a simple example of adding to a state list (haven't run myself, so just check for syntax errors and such):
import React from "react";
import "./App.css";
import { isTemplateElement } from "#babel/types";
class TodoList extends React.Component {
state = {};
constructor(props) {
super(props);
this.state = {
userInput: "",
list: [],
};
}
changeUserInput(input) {
this.setState({
userInput: input
})
}
addToList() {
const { list, userInput } = this.state;
// Add item to state list using spread operator and clear input
this.setState({
list: [...list, {text:userInput, key: Date.now()}],
userInput: ""
});
}
render() {
return (
<div className="to-do-list-main">
<input
onChange={(e) => this.changeUserInput(e.target.value)}
value={this.state.userInput}
type="text"
/>
<button onClick={() => this.addToList()}>Press me</button>
<hr/>
{/* For each item in the list, render the contents */}
{this.state.list.map(item => (
<div key={item.key}>
<h3>{item.text}</h3>
<p>Time: {item.key}</p>
</div>
))}
</div>
);
}
}
export default TodoList;

Refresh a specific component 's data/ state in React

I have a List of products-ID and a button. When I press the button, I want to refresh the data in the ListComponent. I have no idea how can I do this in React. Can someone help me?
constructor(props) {
super(props);
this.state = {
products: this.props.productData //where productData an array of all products-ID
};
this.refresh = this.refresh.bind(this);
}
refresh() {
this.setState({ products: null });
this.forceUpdate();
}
render() {
const { products } = this.state;
<Button onClick={this.refresh} />
<ListComponent
data={products.map(entry => ({
text: entry.productId
}))}
/>
);
}
}
const mapStateToProps = (state, ownProps) => {
const products = selectAllProducts(state); //function that fetches-takes all products
return {
productData: products.map(products => ({
productId: product.get("productId")
}))
};
};
Your refresh function needs to call an action that fetches the data, and updates the Redux store accordingly. And because you've mapped part of your Redux state to this component's props, it will re-render when that data is fetched and saved via the reducer.
Therefore, you don't need to set local state at all in this component. Provided you have an action called fetchProductData:
class ProductList extends React.Component {
constructor (props) {
super(props)
this.refresh = this.refresh.bind(this)
}
// if you don't already have the data in your store, you can fetch it here to kick things off
componentDidMount () {
this.props.fetchProductData()
}
refresh () {
this.props.fetchProductData()
}
render () {
const { products } = this.state
return (
<div>
<Button onClick={this.refresh} />
<ListComponent
data={products.map(entry => ({
text: entry.productId
}))}
/>
</div>
)
}
}
const mapStateToProps = (state, ownProps) => {
const products = selectAllProducts(state)
return {
productData: products.map(products => ({
productId: product.get("productId")
}))
}
}
export default connect(mapStateToProps, { fetchProductData })(MyComponent)
Again, this assumes that fetchProductData dispatches an action that will update the redux state where products are stored. Passing the action to connect like this will make it available as a prop within the component.
It looks like you've placed your refresh() inside the constructor, try:
constructor(props) {
super(props);
this.state = {
products: this.props.productData //where productData an array of all products-ID
};
this.refresh = this.refresh.bind(this);
}
refresh() {
this.setState({ products: null });
this.forceUpdate();
}
render() {
const { products } = this.state;
<Button onClick={this.refresh} />
<ListComponent
data={products.map(entry => ({
text: entry.productId
}))}
/>
);
}
I made a minimal component that does what you want it to do. Instead of binding in the constructor i use a fat arrow function for refresh.
import { Component } from "react";
const ListItem = props => props.item.text;
class List extends Component {
constructor(props) {
super(props);
this.state = {
items: [{ id: 0, text: "zero" }, { id: 1, text: "one" }]
};
}
refresh = () => {
this.setState({ items: [] });
};
render() {
const { items } = this.state;
return (
<div>
{items.map(i => (
<div key={i.id}>
<ListItem item={i} />
</div>
))}
<button onClick={this.refresh}>refresh</button>
</div>
);
}
}
export default List;
You don't need to forceUpdate(), the component will re-render by default when its props are changed.
For an explanation of the fat arrow and what it does to this, check out https://hackernoon.com/javascript-es6-arrow-functions-and-lexical-this-f2a3e2a5e8c4.

How to create shared-singleton components across all the platform?

I'm thinking on creating a React component called LoadingMask, where I can show or not (depending on the state) a loading mask from any component. The idea is showing it before an ajax call, and hiding it after I receive the data.
I don't want to display two masks at the same time, so if one component is making a request, and another one creates another request, I want to add 1 to my "MaskCounter", and substract one when the Request is finished. If the counter is 0, I need to hide the LoadingMask.
I order to do this, I think I need to create a "Singleton" component, that I can share through the whole platform, so there's only exist one LoadingMask. I also don't think it's nice to send the events to hide/show the mask to all components.
Any ideas?
To share data between components, you can :
Use a lib like Redux, and keep in shared store your mask loader status
Use the React context api from your root component, and share loader status to all childrens. See an example below :
class Application extends React.Component {
constructor() {
super();
this.state = {
nbTasks: 0
};
this.addTask = this.addTask.bind(this);
this.removeTask = this.removeTask.bind(this);
this.isLoading = this.isLoading.bind(this);
}
addTask() {
this.setState(prevState => ({
nbTasks: prevState.nbTasks + 1
}));
}
removeTask() {
this.setState(prevState => ({
nbTasks: prevState.nbTasks - 1
}));
}
isLoading() {
return this.state.nbTasks > 0;
}
getChildContext() {
return {
addTask: this.addTask,
removeTask: this.removeTask,
isLoading: this.isLoading
};
}
render() {
return (
<div>
<ComponentX />
<ComponentY />
<LoadingMask />
</div>
);
}
}
Application.childContextTypes = {
addTask: PropTypes.func,
removeTask: PropTypes.func,
isLoading: PropTypes.func
};
const LoadingMask = (props, context) => (
context.isLoading()
? <div>LOADING ...</div>
: null
);
LoadingMask.contextTypes = {
isLoading: PropTypes.func
};
class ComponentX extends React.Component {
constructor(props, context) {
super(props, context);
this.state = {
message: 'Processing ...'
};
}
componentDidMount() {
this.context.addTask();
setTimeout(() => {
this.setState({
message: 'ComponentX ready !'
});
this.context.removeTask();
}, 3500);
}
render() {
return (
<div>
<button disabled>{this.state.message}</button>
</div>
);
}
}
ComponentX.contextTypes = {
addTask: PropTypes.func,
removeTask: PropTypes.func
};
class ComponentY extends React.Component {
constructor(props, context) {
super(props, context);
this.state = {
message: 'Processing ...'
};
}
componentDidMount() {
this.context.addTask();
setTimeout(() => {
this.setState({
message: 'ComponentY ready !'
});
this.context.removeTask();
}, 6000);
}
render() {
return (
<div>
<button disabled>{this.state.message}</button>
</div>
);
}
}
ComponentY.contextTypes = {
addTask: PropTypes.func,
removeTask: PropTypes.func
};
ReactDOM.render(
<Application />,
document.getElementById('app')
);
<script src="https://unpkg.com/prop-types/prop-types.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.6.1/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.6.1/react-dom.min.js"></script>
<div id="app"></app>
I found this library use-between to be simple, powerful and useful. It removes complexity of redux for sharing data between within functional components.
import React, { useState, useCallback } from 'react';
import { useBetween } from 'use-between';
Context/Session.ts
export const useShareableState = () => {
const [count, setCount] = useState(0);
const inc = useCallback(() => setCount(c => c + 1), []);
const dec = useCallback(() => setCount(c => c - 1), []);
return {
count,
inc,
dec
};
};
App.tsx
import { useBetween } from 'use-between';
import { useShareableState } from './src/Context/Session'
const useSharedCounter = () => useBetween(useShareableState);
const Count = () => {
const { count } = useSharedCounter();
return <p>{count}</p>;
};
const Buttons = () => {
const { inc, dec } = useSharedCounter();
return (
<>
<button onClick={inc}>+</button>
<button onClick={dec}>-</button>
</>
);
};
const App = () => (
<>
<Count />
<Buttons />
<Count />
<Buttons />
</>
);
export default App;

Categories

Resources