I am trying to learn how to use react-polls (for further communication with backend, so, some fragments of my code are prepared for next steps). The problem is: I expect to see 123 in my browser (as in componentDidMount), but I don't.
When I pass the string '123' directly instead of const pollQuestion = props => (<div>{props.quest}</div>); it works.
//imports
import Poll from 'react-polls';
const pollQuestion = props => (<div>{props.quest}</div>);
const pollAnswers = [
{ option: 'Yes', votes: 8 },
{ option: 'No', votes: 2 }
]
class PollQuestion extends Component {
state = {
pollAnswers: [...pollAnswers]
}
handleVote = voteAnswer => {
const { pollAnswers } = this.state
const newPollAnswers = pollAnswers.map(answer => {
if (answer.option === voteAnswer) answer.votes++
return answer
})
this.setState({
pollAnswers: newPollAnswers
})
}
render () {
const { pollAnswers } = this.state
return (
<div>
<Poll question={pollQuestion} answers={pollAnswers} onVote={this.handleVote} />
<p>It works</p>
</div>
);
}
};
class QuestionList extends Component {
state = {
questions: []
}
componentDidMount(){
this.setState({ questions: [{question_text:'123'}]});
}
render(){
return (
<div>{this.state.questions?
<ul>
<PollQuestion quest={this.state.questions.slice(0, 1).map(question => <li>{question.question_text}</li>)} />
</ul>:null}
</div>
)
}
};
function AppV() {
return (
<div className="App">
<QuestionList/>
</div>
);
}
export default AppV;
The problem was you were not passing the value of this.props.quest to the pollQuestion element. Either do this
<Poll question={this.props.quest} answers={pollAnswers} onVote={this.handleVote} />
See code here : https://stackblitz.com/edit/react-nngrut
OR do this
Edit
const pollQuestion = props => (<div>{props.quest}</div>); to
const MyPollQuestion = props => (<div>{props.quest}</div>);
<Poll question={<MyPollQuestion quest={this.props.quest} />} answers={pollAnswers} onVote={this.handleVote} />
https://stackblitz.com/edit/react-sxf2kx?file=AppV.js
Also, All react components should start with a capital letter.
The
React-Polls question attribute has Proptype of string
and you are
trying to pass a React Element
to it that is why it is not working.
Pass you question string directly.
Related
I am learning React and I have a solution that requests information through an API, and when there is a response it sets the state, however when rendering my results it only shows the last response on screen,
Even though there are 4, see image below.
App.js
import React from 'react';
import Tiles from './components/Tiles'
import Form from './components/Form'
import WaterData from './components/WaterData'
class App extends React.Component{
state = {
station_name: undefined,
water_value: undefined,
dateTime: undefined
}
getData = async (e) => {
e.preventDefault();
const name = e.target.elements.name.value;
const api_call = await fetch(`https://waterlevel.ie/geojson/latest/`)
.then(response1 => {
response1.json().then(data =>{
Array.from(data.features).forEach(element => {
if(element.properties['station.name'] === name){
this.setState({
station_name: element.properties['station.name'],
water_value: element.properties['value'],
dateTime: element.properties['datetime'],
});
}
})
});
});
}
render(){
return(
<div>
<Tiles />
<Form loadData={this.getData}/>
<WaterData
station_name={this.state.station_name}
water_value={this.state.water_value}
dateTime={this.state.dateTime}
/>
</div>
)
}
}
export default App;
WaterData.js
import React from 'react';
const Weather = (props) => {
console.log(props)
return(
<li>
<p>Location {props.station_name}</p>
<p>Value {props.water_value}</p>
<p>Date Time: {props.dateTime}</p>
</li>
)
}
export default Weather;
Can someone explain to me why the 4 responses do not display?
This happens because you are replacing the values in your state for each part of your data.
You can filter out the element you want in your array using filter.
And then put the whole array into your state only once :
const api_call = await fetch(`https://waterlevel.ie/geojson/latest/`)
.then(response1 => {
response1.json().then(data => {
const features = Array.from(data.features)
.filter(el => el.properties['station.name'] === name);
this.setState({ features });
})
});
But now, to render all of them, you will need to map your state values :
render(){
return(
<div>
<Tiles />
<Form loadData={this.getData}/>
{this.state.features.map(feat => <WaterData
key={/* Find something unique*/}
station_name={feat.properties['station.name']}
water_value={feat.properties['value']}
dateTime={feat.properties['datetime']}
/>)}
</div>
)
}
There's no need to store all the value separately in your state if they are related to each other, it would be fine for your child component though.
To be sure that the state value is always an array, give it an empty array at the start of your class :
state = {
features: []
}
Im making my first react project. Im new in JS, HTML, CSS and even web app programming.
What i want to do it is a Search input label. Now its look like this:
Like you can see i have some list of objects and text input.
I Have two components, my ProjectList.js with Search.js component...
class ProjectsList extends Component {
render() {
return (
<div>
<Search projects={this.props.projects} />
<ListGroup>
{this.props.projects.map(project => {
return <Project project={project} key={project.id} />;
})}
</ListGroup>
</div>
);
}
}
export default ProjectsList;
... and ProjectList.js displays Project.js:
How looks Search.js (its not ended component)
class Search extends Component {
state = {
query: ""
};
handleInputChange = () => {
this.setState({
query: this.search.value
});
};
render() {
return (
<form>
<input
ref={input => (this.search = input)}
onChange={this.handleInputChange}
/>
<p />
</form>
);
}
}
export default Search;
My project have name property. Could you tell me how to code Search.js component poperly, to change displaying projects dynamically based on input in text label? for example, return Project only, if text from input match (i want to search it dynamically, when i start typing m... it shows all projects started on m etc).
How to make that Search input properly? How to make it to be universal, for example to Search in another list of objects? And how to get input from Search back to Parent component?
For now, in react dev tools whatever i type there i get length: 0
Thanks for any advices!
EDIT:
If needed, my Project.js component:
class Project extends Component {
state = {
showDetails: false
};
constructor(props) {
super(props);
this.state = {
showDetails: false
};
}
toggleShowProjects = () => {
this.setState(prevState => ({
showDetails: !prevState.showDetails
}));
};
render() {
return (
<ButtonToolbar>
<ListGroupItem className="spread">
{this.props.project.name}
</ListGroupItem>
<Button onClick={this.toggleShowProjects} bsStyle="primary">
Details
</Button>
{this.state.showDetails && (
<ProjectDetails project={this.props.project} />
)}
</ButtonToolbar>
);
}
}
export default Project;
To create a "generic" search box, perhaps you could do something like the following:
class Search extends React.Component {
componentDidMount() {
const { projects, filterProject, onUpdateProjects } = this.props;
onUpdateProjects(projects);
}
handleInputChange = (event) => {
const query = event.currentTarget.value;
const { projects, filterProject, onUpdateProjects } = this.props;
const filteredProjects = projects.filter(project => !query || filterProject(query, project));
onUpdateProjects(filteredProjects);
};
render() {
return (
<form>
<input onChange={this.handleInputChange} />
</form>
);
}
}
This revised version of Search takes some additional props which allows it to be reused as required. In addition to the projects prop, you also pass filterProject and onUpdateProjects callbacks which are provided by calling code. The filterProject callback allows you to provide custom filtering logic for each <Search/> component rendered. The onUpdateProjects callback basically returns the "filtered list" of projects, suitable for rendering in the parent component (ie <ProjectList/>).
The only other significant change here is the addition of visibleProjects to the state of <ProjectList/> which tracks the visible (ie filtered) projects from the original list of projects passed to <ProjectList/>:
class Project extends React.Component {
render() {
return (
<div>{ this.props.project }</div>
);
}
}
class ProjectsList extends React.Component {
componentWillMount() {
this.setState({ visibleProjects : [] })
}
render() {
return (
<div>
<Search projects={this.props.projects} filterProject={ (query,project) => (project == query) } onUpdateProjects={ projects => this.setState({ visibleProjects : projects }) } />
<div>
{this.state.visibleProjects.map(project => {
return <Project project={project} key={project.id} />;
})}
</div>
</div>
);
}
}
class Search extends React.Component {
componentDidMount() {
const { projects, filterProject, onUpdateProjects } = this.props;
onUpdateProjects(projects);
}
handleInputChange = (event) => {
const query = event.currentTarget.value;
const { projects, filterProject, onUpdateProjects } = this.props;
const filteredProjects = projects.filter(project => !query || filterProject(query, project));
onUpdateProjects(filteredProjects);
};
render() {
return (
<form>
<input onChange={this.handleInputChange} />
</form>
);
}
}
ReactDOM.render(
<ProjectsList projects={[0,1,2,3]} />,
document.getElementById('react')
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.0.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.0.0/umd/react-dom.production.min.js"></script>
<div id="react"></div>
I will assumes both your Search and ProjectList component have a common parent that contains the list of your projects.
If so, you should pass a function into your Search component props, your Search component will then call this function when the user typed something in the search bar. This will help your parent element decide what your ProjectsLists needs to render :
handleInputChange = () => {
this.props.userSearchInput(this.search.value);
this.setState({
query: this.search.value
});
};
And now, here is what the parent element needs to include :
searchChanged = searchString => {
const filteredProjects = this.state.projects.filter(project => project.name.includes(searchString))
this.setState({ filteredProjects })
}
With this function, you will filter out the projects that includes the string the user typed in their names, you will then only need to put this array in your state and pass it to your ProjectsList component props
You can find the documentation of the String includes function here
You can now add this function to the props of your Search component when creating it :
<Search userSearchInput={searchChanged}/>
And pass the filtered array into your ProjectsList props :
<ProjectsList projects={this.state.filteredProjects}/>
Side note : Try to avoid using refs, the onCHnage function will send an "event" object to your function, containing everything about what the user typed :
handleInputChange = event => {
const { value } = event.target
this.props.userSearchInput(value);
this.setState({
query: value
});
};
You can now remove the ref from your code
I have a SearchBar component and it has a subcomponent SearchBarItem.
I passed the method handleSelectItem() to subcomponent to dispatch value to store and it works (I saw it from the Redux tool in Chrome).
Then, when I tried to get the value from the method submitSearch(), which I also passed it from the parent component, it shows:
Cannot read property 'area' of undefined.
I'm still not so familiar with React. If someone can help, it will be very appreciated. Thanks in advance.
This is parent component SearchBar:
class SearchBar extends Component {
handleSelectItem = (selectCategory, selectedItem) => {
if (selectCategory === 'areas') {
this.props.searchActions.setSearchArea(selectedItem);
}
}
submitSearch() {
console.log(this.props.area); // this one is undefined
}
render() {
return (
<div className="searchBar">
<SearchBarItem
selectCategory="areas"
name="地區"
options={this.props.areaOptions}
handleSelectItem={this.handleSelectItem}
submitSearch={this.submitSearch}
/>
</div>
);
}
}
const mapStateToProps = state => ({
area: state.search.area,
brandOptions: state.search.brandOptions,
vehicleTypeOptions: state.search.vehicleTypeOptions,
areaOptions: state.search.areaOptions,
});
const mapDispatchToProps = dispatch => ({
searchActions: bindActionCreators(searchActions, dispatch),
});
export default connect(mapStateToProps, mapDispatchToProps)(SearchBar);
This is subcomponent SearchBarItem:
export default class SearchBarItem extends Component {
state = {
showOptions: false,
selectedItem: [],
}
handleSelectItem = (selectedItem) => this.props.handleSelectItem(this.props.selectCategory, selectedItem);
submitSearch = () => this.props.submitSearch();
handleClickCategory = () => {
this.setState({ showOptions: !this.state.showOptions });
}
handleClickItem(option) {
this.setState({
selectedItem: [...this.state.selectedItem, option],
}, () => this.handleSelectItem(this.state.selectedItem));
}
render() {
const options = this.props.options.map((item, index) => (
<div
className={this.state.selectedItem === item ? "searchBarItem__option--active" : "searchBarItem__option"}
key={index}
onClick={() => this.handleClickItem(item)}
>
{item}
</div>
));
const optionBox = (
<div className="searchBarItem__box">
<div
className="searchBarItem__option"
onClick={() => this.handleClickItem('')}
>
不限{this.props.name}
</div>
{options}
<div className="searchBarItem__confirm">
<span>取消</span><span onClick={() => this.submitSearch()} >套用</span>
</div>
</div>
);
return (
<div className="searchBarItem">
<span onClick={() => this.handleClickCategory()} >
{(() => {
switch (this.state.selectedItem.length) {
case 0: return this.props.name;
case 1: return this.state.selectedItem[0];
default: return `${this.state.selectedItem.length} ${this.props.name}`;
}
})()}
</span>
{ this.state.selectedItem.length > 0 ? '' : <Icon icon={ICONS.DROP_DOWN} size={18} /> }
{ this.state.showOptions ? optionBox : '' }
</div>
);
}
}
SearchBarItem.propTypes = {
name: PropTypes.string.isRequired,
selectCategory: PropTypes.string.isRequired,
options: PropTypes.arrayOf(PropTypes.string).isRequired,
handleSelectItem: PropTypes.func.isRequired,
submitSearch: PropTypes.func.isRequired,
};
Your problem caused by the behavior of this pointer in javascript.
By writing the code submitSearch={this.submitSearch} you are actually sending a pointer to the submitSearch method but losing the this pointer.
The method actually defers as MyClass.prototype.myMethod. By sending a pointer to the method MyClass.prototype.myMethod you are not specifying to what instance of MyClass it belongs to (if at all). This is not the most accurate explanation of how this pointer works but it's intuitive explanation, you can read more here about how this pointer works
You have some possible options to solve it:
Option one (typescript/babel transpiler only) - define method as class variable
class MyClass{
myMethod = () => {
console.log(this instanceof MyClass) // true
}
}
this will automatically do option 2 for you
Option two - Bind the method on the constructor
class MyClass{
constructor(){
this.myMethod = this.myMethod.bind(this)
}
myMethod() {
console.log(this instanceof MyClass) // true
}
}
By the second way, you are binding the method to current this instance
Small note, you should avoid doing:
<MyComponent onSomeCallback={this.myCallback.bind(this)} />
Function.prototype.bind returns a new method and not mutating the existing one, so each render you'll create a new method and it has performance impact on render (binding it on the constructor only once as option two, is fine)
I am learning reactJS and so I am trying my hands on an example. This example has a form textfield that can add an item to an existing array on click of a button. I am having errors here as when I enter a text and click on the button, the array list is not updated except I try to make changes to the text entered in the textfield. This is what I am doing:
import React, { Component } from 'react';
class App extends Component {
constructor(props){
super(props);
this.state = {
currentName : '',
arrays : ['john', 'james', 'timothy']
}
}
render() {
const showNames = this.state.arrays.map((thisName) => {
const values = <li>{thisName}</li>;
return values;
});
const getText = (e) => {
let value = e.target.value;
this.setState({
currentName : value
})
}
const addToUsers = () => {
this.state.arrays.push(this.state.currentName)
}
return (
<div>
<p>Add new name to List</p><br/>
<form>
<input type="text" onChange={getText}/>
<button type="button" onClick={addToUsers}>Add User</button>
</form>
<ul>
{showNames}
</ul>
</div>
);
}
}
export default App;
There are a host of things wrong with this, but your issue is likely that you need to use setState to modify state.
import React, { Component } from 'react';
class App extends Component {
constructor(){
super();
this.state = {
names: ['john', 'james', 'timothy']
}
}
addToUsers = () => {
this.setState(
prevState => ({
names: [...prevState.names, this.input.value]
})
)
}
render() {
const names = this.state.names.map(
(name, index) => <li key={index}>{name}</li>
)
return (
<div>
<p>Add new name to List</p><br/>
<form>
<input type="text" ref={e => this.input = e} />
<button type="button" onClick={this.addToUsers}>Add User</button>
</form>
<ul>
{names}
</ul>
</div>
)
}
}
export default App;
This quick edit changes a few things:
Uses setState for the addToUsers method
Eliminate onChange tracking and pull the name directly from the input when the button is clicked
Move the addToUsers method out to the component class rather than defining it on render
Rename this.state.arrays to this.state.names
Simplify conversion of this.state.names into list items
Set key on array elements (name list items)
Use prevState in setState to avoid race conditions
You need to make sure you update state using the setState method.
When you update arrays you are reaching into the state object and manipulating the data directly instead of using the method.
Instead try something like:
const addToUsers = () => {
const newArray = this.state.arrays.concat([this.state.currentName]);
this.setState({
arrays: newArray
});
}
You probably must add
onChange={getText}.bind(this)
to your functions.
Also change this
const addToUsers = () => {
this.state.arrays.push(this.state.currentName)
}
to this
const addToUsers = () => {
this.setState({here put your variable})
}
I'm using React to render multiple data using array.map.
How can disable the clicked button from the list?
This is my code:
onRunClick(act, e) {
this.refs.btn.setAttribute("disabled", true);
}
render () {
return (
<div>
{
this.state.acts.map((act) => {
let boundActRunClick = this.onRunClick.bind(this, act);
return (
<p key={act._id}>
Name: {act.name}, URL(s): {act.urls}
<button ref='btn' onClick={boundActRunClick}>Run</button>
</p>
)
})
}
</div>
);
}
}
Using refs doesn't work ... I think that I can't add a state since there are multiple buttons.
You should use ref callback instead of ref and also yes you need multiple refs, an array should be good
According to the docs:
React supports a special attribute that you can attach to any
component. The ref attribute takes a callback function, and the
callback will be executed immediately after the component is mounted
or unmounted.
When the ref attribute is used on an HTML element, the ref callback
receives the underlying DOM element as its argument.
ref callbacks are invoked before componentDidMount or
componentDidUpdate lifecycle hooks.
Using the ref callback just to set a property on the class is a common
pattern for accessing DOM elements. The preferred way is to set the
property in the ref callback like in the above example. There is even
a shorter way to write it: ref={input => this.textInput = input}.
String refs are a legacy and and as per the docs:
Legacy API: String Refs
If you worked with React before, you might be familiar with an older
API where the ref attribute is a string, like "textInput", and the DOM
node is accessed as this.refs.textInput. We advise against it
because string refs have some issues, are considered legacy, and are
likely to be removed in one of the future releases. If you’re
currently using this.refs.textInput to access refs, we recommend
the callback pattern instead.
constructor() {
super();
this.btn = [];
}
onRunClick(act, index, e) {
this.btn[index].setAttribute("disabled", true);
}
render () {
return (
<div>
{
this.state.acts.map((act, index) => {
let boundActRunClick = this.onRunClick.bind(this, act, index);
return (
<p key={act._id}>
Name: {act.name}, URL(s): {act.urls}
<button ref={(el) => this.btn[index] = el} onClick={boundActRunClick}>Run</button>
</p>
)
})
}
</div>
);
}
Like #ShubhamKhatri's answer using ref is an option. You can also achieve desired behavior with state too.
Example (Single Disabled Button Option)
class App extends Component{
constructor() {
super();
this.state = {
disabled: ''
};
}
onRunClick(act, index, e) {
this.setState({ disabled: act._id });
}
render() {
return (
<div>
{
this.state.acts.map((act, index) => {
let boundActRunClick = this.onRunClick.bind(this, act, index);
return (
<p key={act._id}>
Name: {act.name}, URL(s): {act.urls}
<button disabled={this.state.disabled === act._id} onClick={boundActRunClick}>Run</button>
</p>
)
})
}
</div>
);
}
}
Example (Multiple Disabled Button Option)
class App extends Component{
constructor() {
super();
this.state = {
buttons: {}
};
}
onRunClick(act, index, e) {
this.setState((prevState) => {
const buttons = Object.assign({}, prevState.buttons, { [act._id]: !prevState.buttons[act._id] });
return { buttons };
});
}
render() {
return (
<div>
{
this.state.acts.map((act, index) => {
let boundActRunClick = this.onRunClick.bind(this, act, index);
return (
<p key={act._id}>
Name: {act.name}, URL(s): {act.urls}
<button disabled={this.state.buttons[act._id] || false} onClick={boundActRunClick}>Run</button>
</p>
)
})
}
</div>
);
}
}
For function components (React 16+), you can approach it like the following:
/*
* #param {Object|Function} forwardedRef callback ref function or ref object that `refToAssign` will be assigned to
* #param {Object} refToAssign React ref object
*/
export function assignForwardedRefs(forwardedRef, refToAssign) {
if (forwardedRef) {
if (typeof forwardedRef === 'function') {
forwardedRef(refToAssign)
} else {
forwardedRef.current = refToAssign
}
}
}
function MyComponent({
forwardedRef
}) {
const innerRef = useRef()
function setRef(ref) {
assignForwardedRefs(forwardedRef, ref)
innerRef.current = ref
}
return <div ref={setRef}>Hello World!</div>
}
export default React.forwardRef((props, ref) => <MyComponent {...props} forwardedRef={ref} />)
You can use the npm module react-multi-ref (a tiny library by me) to do this.
import React from 'react';
import MultiRef from 'react-multi-ref';
class Foo extends React.Component {
_actRefs = new MultiRef();
onRunClick(act, e) {
this._actRefs.map.get(act._id).setAttribute("disabled", true);
}
render () {
return (
<div>
{
this.state.acts.map((act) => {
let boundActRunClick = this.onRunClick.bind(this, act);
return (
<p key={act._id}>
Name: {act.name}, URL(s): {act.urls}
<button ref={this._actRefs.ref(act._id)} onClick={boundActRunClick}>Run</button>
</p>
)
})
}
</div>
);
}
}
Though in this specific case where you just want to change an attribute on an element, instead of using a ref you should do it through state and props on the <button> through React as in the answer by #bennygenel. But if you need to do something else (call an imperative DOM method on the button, read the value of an uncontrolled input element, read the screen position of an element, etc) then you'll need to use a ref like this.