I'm getting this error when trying to use ReactiveSearch for a search bar. This is how I'm initialising it:
render() {
const { tenantConfig, size, componentId } = this.props;
return (
<ReactiveComponent
componentId={componentId}
defaultQuery={this.defaultQuery}
>
<SearchDropdownDashboard
size={size}
handleSearchDashboard={this.handleSearchDashboard}
fetching={this.state.fetching}
tenantConfig={tenantConfig}
/>
</ReactiveComponent>
);
}
And this is the function that is being passed in:
defaultQuery = () => {
const { dashboardText } = this.state;
const { mustNotObj } = this.props;
let obj;
obj = {
query: {
bool: {
must_not: mustNotObj,
must: multiMatchSearch(dashboardText)
}
},
from: 0,
size: 20
};
return obj;
};
Any suggestions as to what I'm doing wrong here? The function seems to be passed correctly to the component.
If you are using v3, then it is due to the recent changes introduced in API. You will need to use render prop or React render Pattern as done in the below example.
You can check the docs here: https://opensource.appbase.io/reactive-manual/advanced/reactivecomponent.html#usage-with-defaultquery.
I have created the example of Usage of ReactiveComponent on both the versions:
v3 : https://codesandbox.io/s/serene-ritchie-rjo3m
v2 : https://codesandbox.io/s/tender-ramanujan-f3g31
Hope this helps!
Related
I'm trying to make an update on an Object that is in the state using React Hooks, but when I try to update the Objet the function .map stop working.
First I have this handleCheckedFilterChange function and it change the state Object but the component was not re-render:
const handleCheckedFilterChange = (name) => {
let prevFilters = filters;
for (let filter in prevFilters) {
if (prevFilters[filter].name === name) {
prevFilters[filter].checked = !prevFilters[filter].checked;
prevFilters[filter].filters.map((value) => {
value.disabled = !value.disabled;
});
setFilters(prevFilters);
break;
}
}
};
So then I change it to this one to make the component render again:
const handleCheckedFilterChange = (name) => {
let prevFilters = { ...filters };
for (let filter in prevFilters) {
if (prevFilters[filter].name === name) {
prevFilters[filter].checked = !prevFilters[filter].checked;
prevFilters[filter].filters.map((value) => {
value.disabled = !value.disabled;
});
setFilters(prevFilters);
break;
}
}
};
But this second one generates me an error:
TypeError: filters.map is not a function
The error is when I call the following:
const renderCards = filters.map((filter) => (
<div className="card text-center">
...
</div>
));
In the first option you are trying to modify the state directly which is not allowed in React's useState and that's why it is not rendering your expected change.
The problem with the second option is your are trying to use {} instead of []. Try as:
let prevFilters = [ ...filters ];
The reason behind is .map() is a function on arrays and not on objects.
EDIT: since the code snip does not reproduce the bug - here is a link to the github repo: (code is far FAR from complete)
https://github.com/altruios/clicker-game
I have run it on two computers now - both with the same behavior the code snip doesn't show.
//interestingly enough, this works just fine, where the same code I run locally has the doubling.
//when I comment out ALL other code except for this code I STILL get the error locally
//at this point the only difference is import export of components... here they are in one file.
//below is original code from file (
/*
FILE::::Clicker.js
import React from 'react';
function Clicker(props)
{
return(
<div>
{props.name}
<button
name={props.name}
onClick={props.HandleClick}
data-target={props.subjectsOfIncrease}>
{props.name} {props.value}
</button>
</div>
)
}
export default Clicker;
FILE:: Resouce.js
import React from 'react';
function Resource(props)
{
return(
<div>
{props.name} and {props.amount || 0}
</div>
)
}
export default Resource;
*/
//besides the import/export and seprate files - code is the same. it works in here, does not work locally on my machine.
const gameData = {
clickerData: [{
name: "grey",
subjectsOfIncrease: ["grey"],
isUnlocked: true,
value: 1
}],
resourceData: [{
name: "grey",
resouceMax: 100,
isUnlocked: true,
changePerTick: 0,
counterTillStopped: 100,
amount: 0
}]
}
class App extends React.Component {
constructor() {
super();
this.state = {
resources: gameData.resourceData,
clickers: gameData.clickerData
};
this.gainResource = this.gainResource.bind(this);
}
gainResource(event) {
console.count("gain button");
const name = event.target.name;
this.setState((prevState) => {
const newResources = prevState.resources.map(resource => {
if (resource.name === name) {
resource.amount = Number(resource.amount) + 1 //Number(prevState.clickers.find(x=>x.name===name).value)
}
return resource;
});
console.log(prevState.resources.find(item => item.name === name).amount, "old");
console.log(newResources.find(item => item.name === name).amount, "new");
return {
resources: newResources
}
});
}
render() {
const resources = this.state.resources.map(resourceData => {
return (
<Resource
name = {resourceData.name}
resouceMax = {resourceData.resourceMax}
isUnlocked = {resourceData.isUnlocked}
changePerTick = {resourceData.changePerTick}
counterTillStopped = {resourceData.countTillStopped}
amount = {resourceData.amount}
key = {resourceData.name}
/>
)
})
const clickers = this.state.clickers.map(clickerData => {
return (
<Clicker
name = {clickerData.name}
HandleClick = {this.gainResource}
value = {clickerData.amount}
key = {clickerData.name}
/>
)
})
return (
<div className = "App" >
{resources}
{clickers}
</div>
)
}
}
function Resource(props) {
return <div > {props.name} and {props.amount || 0} </div>
}
function Clicker(props) {
return (
<div > {props.name}
<button name = {props.name} onClick = {props.HandleClick}>
{props.name} {props.value}
</button>
</div>
)
}
const root = document.getElementById('root');
ReactDOM.render( <App / >,root );
<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"></div>
so I'm building a clicker game to learn react, and something I don't understand why this code is behaving the way it does:
in the main app I have this function:
gainResource(event)
{
console.count("gain button");
const name = event.target.name;
this.setState( (prevState)=>
{
const newResources = prevState.resources.map(resource=>
{
if(resource.name === name)
{
resource.amount = Number(resource.amount) + 1 //Number(prevState.clickers.find(x=>x.name===name).value)
}
return resource;
});
console.log(prevState.resources.find(item=>item.name===name).amount, "old");
console.log(newResources.find(item=>item.name===name).amount, "new");
return {resources: newResources}
});
}
that console.count runs a single time... but I get 2 'old and new' pairs. as if setState is running twice in this function which only runs once?
the console.output is:
App.js:64 gain button: 1
App.js:76 1 "old"
App.js:77 1 "new"
App.js:76 2 "old"
App.js:77 2 "new"
so it looks like the function is running once. but the set state is being run twice?
the symptoms are that it counts up by 2. but also the initial state of amount is 0, not 1, as seen in the gamedata.json
resourceData:
[
{
name:"grey",
resouceMax:100,
isUnlocked:true,
changePerTick:0,
counterTillStopped:100,
amount:0
},{etc},{},{}],
clickerData:
[
{
name:"grey",
subjectsOfIncrease:["grey"],
isUnlocked:true,
value:1
},{etc},{},{}]
i don't think the rest of the code I'm about to most is relevant to this behavior, but I don't know react yet, so I don't know what I'm missing: but this is how I'm generating the clicker button:
const clickers = this.state.clickers.map(clickerData=>
{
return(
<Clicker
name={clickerData.name}
HandleClick = {this.gainResource}
value = {clickerData.amount}
key={clickerData.name}
/>
)
})
and in the clicker.js functional component I'm just returning this:
<div>
{props.name}
<button name={props.name} onClick={props.HandleClick}>
{props.name} {props.value}
</button>
</div>
the function is bound to this in the constructor... I don't understand why this is running setState twice inside a function that's called once.
I've also tried:
<div>
{props.name}
<button name={props.name} onClick={()=>props.HandleClick}> //anon function results in no output
{props.name} {props.value}
</button>
</div>
This is an intended behavior of a setState(callback) method wrapped in a <React.Strict> component.
The callback is executed twice to make sure it doesn't mutate state directly.
as per: https://github.com/facebook/react/issues/12856#issuecomment-390206425
In the snippet, you create a new array, but the objects inside of it are still the same:
const newResources = lastResources.map(resource => {
if(resource.name === name){
resource.amount = Number(resource.amount) + 1
}
return resource;
}
You have to duplicate each object individually:
const newResources = lastResources.map(resource => {
const newObject = Object.assign({}, resource)
if(resource.name === name){
newObject.amount = Number(newObject.amount) + 1
}
return newObject;
}
BEST ANSWER:
I was using create-react-app. and my App Component was wrapped in Strict mode... which fires setState twice... which perfectly explains why this was not reproducible on the code snip, and why the function was being called once, yet setState was called twice.
removing strict mode fixed the issue completely.
As long as you didn't provide us a runnable example I've one doubt about what could be happened and let's see if it works.
What I can see is in the gainResource function and specially in this line resource.amount = Number(resource.amount) + 1 you're trying to update the state without using setState which is not recommended by React Documentation
Please instead try first to assign a const myRessource = ressource then return myRessource instead.
gainResource(event)
{
console.count("gain button");
const name = event.target.name;
this.setState( (prevState)=>
{
const newResources = prevState.resources.map(resource=>
{
const myRessource = ressource;
if(myRessource.name === name)
{
myRessource.amount = Number(myRessource.amount) + 1 //Number(prevState.clickers.find(x=>x.name===name).value)
}
return myRessource;
});
console.log(prevState.resources.find(item=>item.name===name).amount, "old");
console.log(newResources.find(item=>item.name===name).amount, "new");
return {resources: newResources}
});
}
okay... so after some hair pulling... I found out a way that works... but I DON'T think this is 'best practice' but it now works for me when I write this:
gainResource(event)
{
const name = event.target.name;
const lastResources = this.state.resources.slice();
const newResources = lastResources.map(resource=>
{
if(resource.name === name)
{
resource.amount = Number(resource.amount) + 1 //Number(prevState.clickers.find(x=>x.name===name).value)
}
return resource;
});
this.setState({resources: newResources});
}
vs
gainResource(event)
{
console.count("gain button");
const name = event.target.name;
this.setState( (prevState)=>
{
const newResources = prevState.resources.map(resource=>
{
if(resource.name === name)
{
resource.amount = Number(resource.amount) + 1 //Number(prevState.clickers.find(x=>x.name===name).value)
}
return resource;
});
console.log(prevState.resources.find(item=>item.name===name).amount, "old");
console.log(newResources.find(item=>item.name===name).amount, "new");
return {resources: newResources}
});
}
that setState without the function of prevState is called once... whereas with the prevState it's called twice... why?
so I still don't understand why setState using a function with prevState is causing two function calls within a function that's called only once... I have read that I should be using prev state as this.state.resources.slice(); just takes an 'untimed snapshot' and could be unreliable. is this true... or is this methodology acceptable?
this is AN answer to anyone else struggling with this. hopefully a better answer can be posted after this enlightenment to what might be happening.
Ok took me a bit of time to figure this out. As others have mentioned your call back needs to be idempotent. the thing to realise here is that react passes the same state object instance into your callback each time it calls it. hence if you change the state object on the first call it will be different on the second call and your callback function will not be idempotent.
this.setState((state) =>
{
//state.counter will have the same value on the first and second
//time your callback is called
return { counter: state.counter + 1};
});
this.setState((state) =>
{
//state.counter will have a value of n+1 the second time it is called
//because you are changing the sate object. This will have the net effect
//of incrementing state.counter by 2 each time you call this.setState!!
state.counter = state.counter + 1;
return { counter: state.counter};
});
The above is probably obvious but this situation becomes less obvious when dealing with arrays. for eg
this.setState((state) =>
{
//even though we are creating a new array, the
//objects in the array have just been copied
//so changing them is probelmatic
newArray = [...state.someArray];
//this is ok as we are replacing the object at newArray[1]
newArray[1] = {objectField : 1};
//this is not ok
newArray[1].objectField = 1;
return { someArray: newArray};
});
I am a noob in React.
I am trying to get a variable that comes from another component.
this is in a js file for the MapLeaflet component
const date = props => {
return (
props.date);
};
And the first component (the one where the variable is date is created) is :
import React, { Component } from 'react';
import DateTimePicker from 'react-datetime-picker';
import MapLeaflets from './MapLeaflet';
class Picker extends Component {
state = {
date: new Date(),
}
onChange = date => this.setState({ date },
function(){
console.log("this works: " + this.state.date);
//const DateContext = React.createContext(this.state.date);
const DateContext =this.state.date
})
render() {
return (
<div>
<DateTimePicker
onChange={this.onChange}
value={this.state.date}
/>
{ console.log(this.state.date) }
<MapLeaflets date = {this.state.date}
/>
)}
</div>
);
}
}
export default Picker;
Here is my log error :
TypeError: undefined is not iterable (cannot read property
Symbol(Symbol.iterator))
I have extensively searched stackoverflow. This appears to be a pretty simple problem, yet it does not work here. I can read use child/parents, or context, but i did not manage to make it work. I did not try redux, but i guess this would be overkill to just pass a props.
Any observation or suggestion would be valuated.
EDIT
Thanks for your answer. Actually the error log comes from this line in the first component :
<MapLeaflets date = {this.state.date}
/>
Does anybody knows why it does not work ?
I am editing also to include a part of the mapleaflet component, just so you understand what i want to do with this date.
refreshStationsList() {
const { updateFavStationsList, readStoredFav } = this.props;
// console.log('refresh');
const date = (props) => {
return (
<div>{props.date}</div>
)
}
const request = `https:url`+date;
this.setState({ isLoading: true });
const favStationsId = readStoredFav();
axios.get(request)
.then(result => {
const stationsList = result.data.map(
station => {
const isFavorite = favStationsId.includes(station.number);
return { ...station, isFavorite: isFavorite }
}
);
this.setState({
stationsList: stationsList,
isLoading: false
})
updateFavStationsList(stationsList);
})
.catch(error => this.setState({
apiDataError: error,
isLoading: false
}));
}
render() { ....etc
Your MapLeaflets component should look like this
const MapLeaflets = (props) => {
return (
<div>{props.date}</div>
)
}
export default MapLeaflets;
You are trying to loop on undefined, first make sure that you are getting data from your api. and always try to do type check before proceeding.
You can use typeof to do type check
After going through GatsbyJS and React tutorials I got the impression that JavaScript expressions are always evaluated when they are inside {} brackets in JSX. But now I'm looking at a JSX file inside a GatsbyJS starter repo, where it looks like the brackets cause different behavior:
const {
data: {
posts: { edges: posts },
site: {
siteMetadata: { facebook }
}
}
} = props;
(Source)
According to the tutorials, "facebook" should be evaluated as JavaScript and should return undefined, but that's not what's happening. Somehow we end up with a JavaScript object data.site.siteMetadata.facebook, which has some data. What's going on here? Why is "facebook" not evaluated as a JavaScript expression?
The bit of code you copy actually has nothing to do with JSX (see below). It's ES6 object destructuring syntax, like #PrithvirajSahu commented on the question.
Say you have an object like this:
const obj = {
a: 100,
b: {
value: 200,
}
};
You can get the inner values like so:
const { a } = obj;
// same as const a = obj.a
const { b: c } = obj;
// same as const c = obj.b
const { b: { value } } = obj;
// same as const value = obj.b.value
const { b: { value: v } } = obj;
// same as const v = obj.b.value
const { a, { b: { value } } } = obj;
// same as
// const a = obj.a;
// const value = obj.b.value;
So back to your piece of code, it's equivalent to
const posts = props.data.posts.edges;
const facebook = props.data.site.siteMetadata.facebook;
As you have found out, the destructuring syntax is very neat at 1 or maybe 2 levels, but hard to read when there're more. Personally, I only use it at 1 level.
Edit: In the function in the source, only the lines starting with <... is JSX syntax.
const CategoryPage = props => {
// code here is normal js
const { ... } = props;
// JSX start from inside this return function
return (
<React.Fragment>
{ /* code between bracket in this section will be evaluate as 'normal JS' */ }
</React.Fragment>
)
}
Caveat: The code between bracket in JSX has to evaluate to a function. If we write something like this:
<div className="container">
Hello
{"Happy"}
World
</div>
Babel will turn it into the following regular JS:
React.createElement(
"div",
{ className: "container" },
"Hello",
"Happy",
"World"
);
Play with babel here
Whatever we put between bracket will be passed to React.createElement as a child of the div element; therefore only valid React element can be placed here:
Null (render nothing)
A string (will become a DOM text node)
Another React element
An expression/function that evaluates to, or returns any of the above
<div>
{ hasDate && <Date /> }
<div>
or
// somewhere in the code
const showDate = (hasDate) => {
if (!hasDate) return null;
return <Date />
}
// in the render function
return (
<div>
{ showDate(hasDate) }
<div>
)
We also can use bracket to pass value to a element's props:
<div
style={ { color: 'red' } }
onClick={ (event) => {...} }>
{ hasDate && <Date /> }
<div>
In the following code, when setState is called from campaignsUpdated, render gets logged to the console, but not renderRow:
var React = require('react-native'),
Bus = require('../Bus'),
styles = require('../Styles'),
CampaignsStore = require('../stores/Campaigns'),
CampaignItem = require('./CampaignItem'),
{
Component,
Text,
TextInput,
ListView,
View,
NavigatorIOS,
ActivityIndicatorIOS
} = React
class CampaignList extends Component {
constructor(props) {
super(props)
this.state = {
dataSource: new ListView.DataSource({rowHasChanged: (r1, r2) => r1 !== r2})
}
}
componentDidMount() {
this.addListeners()
Bus.emit('campaigns:search', '')
}
componentWillUnmount() {
this.removeListeners()
}
render() {
console.log('render')
return (
<View style={styles.container}>
<TextInput
style={styles.searchInput}
placeholder='Campaign Name'
value={this.state.campaignName}
onChange={this.campaignSearchChanged.bind(this)}/>
<ListView
dataSource = {this.state.dataSource}
renderRow = {this.renderRow.bind(this)}/>
</View>
)
}
renderRow(campaign) {
console.log('renderRow')
return <CampaignItem campaign={campaign}/>
}
addListeners() {
Bus.on({
'campaigns:updated': this.campaignsUpdated.bind(this)
})
}
removeListeners() {
Bus.off({
'campaigns:updated': this.campaignsUpdated.bind(this)
})
}
campaignsUpdated(event) {
var campaigns = event.data
this.setState({
dataSource: this.state.dataSource.cloneWithRows(campaigns)
})
}
campaignSearchChanged(event) {
var campaignName = event.nativeEvent.text
Bus.emit('campaigns:search', campaignName)
}
}
module.exports = CampaignList
What am I doing wrong here?
You are passing ListView a function renderRow that returns a component. You would have to call that function within ListView once it is passed, presumably during a map over campaigns.
By the looks of it the most likely case is that you have a classic React mutability issue here.
I.e. I suspect your 'campaignsUpdated' method is called with either the same Array instance it received last time, or the elements within the list are the same instances.
Try using:
campaignsUpdated(event) {
var campaigns = event.data.slice(); // <-- clone the array
this.setState({
dataSource: this.state.dataSource.cloneWithRows(campaigns)
})
}
If that doesn't work, then you either make the part that manages your list of compaigns create new copies when changes are made (e.g. const clone = {...campaign, title:"A new Title"}) or update your rowHasChanged method to see if the title (or whatever data you need) has actually changed.
Here are two really good videos about immutability in JavaScript here:
https://egghead.io/lessons/javascript-redux-avoiding-array-mutations-with-concat-slice-and-spread
https://egghead.io/lessons/javascript-redux-avoiding-object-mutations-with-object-assign-and-spread