Im trying to integrate the < ChipInput /> component from https://github.com/TeamWertarbyte/material-ui-chip-input I'm using material-UI react component and so far what I have is:
A search input bar. When I type an artist it returns a result. so basically is working.
Now, when I try to implement < ChipInput /> , following the instructions I get no results. (note IconButton and TextField are commented out as in Im trying to replace them with ChipInput)
Therefore, if I type "aerosmith" I'll get:
FETCH_URL https://itunes.apple.com/search?term=&limit=4 instead of
FETCH_URL https://itunes.apple.com/search?term=aerosmith&limit=4
so , it's like is not taking my setState query for a reason. I tried componentWillReceiveProps but it didnt help. Any suggestions ?
class Searcher extends React.Component {
constructor(props){
super(props);
this.state = {
query: [],
application: null,
}
}
componentDidMount () {
this.handleSearchRequest();
}
handleSearchRequest() {
console.log('this.state', this.state);
// we will replace BASE_URL with Anu's search api
const BASE_URL = 'https://itunes.apple.com/search?';
const FETCH_URL = BASE_URL + 'term=' + this.state.query + '&limit=4';
console.log('FETCH_URL', FETCH_URL);
fetch(FETCH_URL, {
method: 'GET'
})
// Initial test to see what the console response is after hiting the API
// .then(response => console.log('response', response));
.then(response => response.json())
// const applications = response.data
// this.setState({applications})
//testing the json results
.then(json => {
// console.log('json', json)
const application = json.results[0];
this.setState({application})
console.log({application})
});
}
handleChange(event) {
this.setState({ query: event.target.value})
}
render () {
return (
<div style={{position: 'relative'}}>
{/* <IconButton
iconStyle={styles.smallIcon}
style={styles.iconButton}
onClick={() => this.handleSearchRequest()}
>
<Search color={black} />
</IconButton>
<TextField
underlineShow={false}
id="searchId"
value={this.state.query}
fullWidth={true}
style={styles.textField}
inputStyle={styles.inputStyle}
hintStyle={styles.hintStyle}
onChange={event => {this.setState({ query: event.target.value}) }}
onKeyPress={event => {
if (event.key === 'Enter') {
this.handleSearchRequest()
}
}}
/> */}
<br/>
<br/>
<ChipInput
fullWidth={true}
defaultValue={this.state.query}
onChange={(event) => this.handleChange(event)}
/>
{
this.state.application != null
?
<ResultItem
{...this.props} {...this.state}
application={this.state.application}/>
: <div></div>
}
</div>
);
}
}
export default Searcher;
EDIT:
By the way, if I uncomment from < IconButton /> (line 110) til the end of < TextField /> (line 133) it does exactly what I want , but no chips ( with no ChipInput of course)
You dont need a defaultValue all you need is 4 things (if you want autocomplete) searchText, dataSource, onUpdateInput and Onchange.
Material UI autocomplete and chips properties are similar so apply them to ChipInput, they are almost the same.
Bottom line is you have to write a function for every property you use within ChipInput expect for searchText, which can actually be a string. As you can see, ChipInput might be easy with hardcoded values, but when you start hitting APIS it is not so easy anymore. it is important to realize what onUpdateInput does
Also, you are supposed to bind every function you write within the constructor this is a react pattern that ensures performance, found it on a book.
constructor(props) {
super (props)
this.onUpdateInput = this.onUpdateInput.bind(this);
this.onNewRequest = this.onNewRequest.bind(this);
}
Then on the render method
<ChipInput
searchText={this.state.query}
fullWidth={true}
dataSource={this.state.dataSource}
onUpdateInput={this.onUpdateInput}
onChange={this.onNewRequest}
/>
Related
I'm currently learning React and i am working through 'The Road to React' by Robin Wieruch.
I've just refactored some code from a stateful to a functional stateless component like so:
function Search(props) {
const { value, onChange, children } = props;
return (
<form>
{children} <input
type="text"
value={value}
onChange={onChange}
/>
</form>
);
}
Gets Refactored to:
const Search = ({ value, onChange, children }) => {
<form>
{children} <input
type="text"
value={value}
onChange={onChange}
/>
</form>
}
However nothing is rendering anymore. Are functional stateless components called the same was as stateful ones?
This is how I'm calling the Search component in the App class:
render() {
const { searchTerm, list } = this.state;
return (
<div className="App">
<Search
value = { searchTerm }
onChange = { this.onSearchChange }
>
Search
</Search>
<Table
list = { list }
pattern = { searchTerm }
onDismiss = { this.onDismiss }
/>
</div>
)
I'm not receiving an error at all, so i'm not getting much that's pointing me in the right direction, i'm hoping i'm just missing something silly.
Thanks in advance!
In both cases, it's a stateless function only as there's no state and it's not an class component either.
1st case is working correctly because it's returning the element with the return keyword.
2nd refactored case is also correct but you are not returning anything you need to return the element for it to be rendered.
return example
const func = () => {
... // any more calculations or code
return ( // you are returning the element here
<div>
...
</div>
)
}
If there's no calculation or any additional code and you have to return only an element you can directly return it by using (...) instead of {...} as follows
const func = () => ( // you are directly returning element
<div>
...
</div>
)
PS: for more info you can check into arrow functions
I have a TextField and a Button (both are material-ui components) displaying on my main page. I want to be able to click the button and populate a form that includes the previous TextField and any text that had already been written in it. The code I currently have just makes a new instance of the TextField within the form, while keeping the original TextField as well. How can I bring the existing TextField over into the form without duplicating?
FormTextField.js
const FormTextField = props => {
return (
<TextField
fullWidth={true}
multiline={true}
rows="10"
variant="outlined"
>
{props.data}
</TextField>
)
}
export default class FormTextField extends Component {
render() {
data={this.props.data} />
return (
{FormTextField}
);
}
};
Form.js
const Form= () => {
return (
<FormLabel">Input Text...</FormLabel>
<FormTextField />
);}
export default Form;
App.js
const AddButton= (props) => {
return (
<Button variant="contained" onClick={props.onClick}>
New Interaction
</Button>
)
}
export default class App extends Component {
constructor(props) {
super(props);
this.state = {show: false};
}
showForm = () => {
this.setState({
show: true,
});
}
render() {
return(
<Fragment>
<Header />
<FormTextField />
<AddButton onClick={this.showInteractionForm} />{this.state.show ?
<Form /> : null}
</Fragment>
);
}
};
As you want to share data between two components you can resolve this in different ways, based in your code, a solution could be:
Your App control the data so,
in your state can add:
this.state = {
inputData = '';
}
You need to pass an update function to your FromTextField
<FormTextField onTextUpdate={(text) => this.setState({ inputData: text })} />
Your form field must be controlled by App so you need to pass the data to be shown too:
<FormTextField data={this.state.inputData} onTextUpdate={(text) => this.setState({ inputData: text })} />
(you need to add that modification to FormTextField, they are easy)
And the last step is to do the same with Form
<Form data={this.state.inputData} onTextUpdate={(text) => this.setState({ inputData: text })} />
Inside Form you need to pass data and onTextUpdate to the FormTextField
You can refactor (text) => this.setState({ inputData: text }) to be a method in class.
EDIT
https://codesandbox.io/embed/stackoverflow-form-react-9188m you can find the implementation about I told you before.
I have a comment box in my React App and I built it with Draft.js as follows. I enter some text and format them with bold and italic buttons. Then I click Comment button. After clicking the Comment button, sendUserComment function will be fired and update the state 'newComment'. AXIOS will send them to the database and return the comment so I can show the comment in the 'newCommentShow' div.
The problem is, if I type a comment as a text and apply bold, the data will be sent to the database as
<p><b>Some texts</b></p>
and the returning data is processing as the same way. So I'm seeing the whole <p><b>Some texts</b></p> as a string in the 'newCommentShow' div. How can I process tags in that string and show the correct formatted text?
const { Toolbar } = toolbarPlugin;
const plugins = [toolbarPlugin];
const text = '';
class ProjectModal extends Component {
constructor(props) {
super(props);
this.state = {
newComment: '',
editorState: createEditorStateWithText(text)
};
this.editorOnChange = this.editorOnChange.bind(this);
}
editorOnChange(editorState) {
this.setState({ editorState });
}
sendUserComment = () => {
const com = stateToHTML(this.state.editorState.getCurrentContent())
API.post('add_comment', com)
.then(({ data }) => {
console.log(data);
this.setState({ newComment: data.comment })
})
.catch((err) => {
console.log("AXIOS ERROR: ", err);
})
}
render() {
return(
<div className="newCommentBox">
<div className="newCommentMain">
<Toolbar>
{
(externalProps) => (
<div className="toolbarModal">
<BoldButton {...externalProps} />
<ItalicButton {...externalProps} />
</div>
)
}
</Toolbar>
<Editor
editorState={this.state.editorState}
onChange={this.editorOnChange}
plugins={plugins}
ref={(element) => { this.editor = element; }}
className="editor"
/>
</div>
<button className="commentBtn" onClick={() => this.sendUserComment}>Comment</button>
<div className="newCommentShow">{this.state.newComment}</div>
</div>
)
}
}
To inject HTML directly into a React component can use dangerouslySetInnerHTML
<div className="newComment" dangerouslySetInnerHTML={{ __html: this.state.newComment }} />
Note - as per the docs, use this feature with caution and be sure you do not leave yourself vulnerable to XSS attacks.
I am a beginner in ReactJS and I am getting an error that I don't understand. This is my first written with ReactJS app. Here is my code.
Error
react map is not a function error in my app
SearchBar Component
import RecipeList from './recipes_list';
class SearchBar extends Component {
state = {
term : []
}
onInputChange(term){
this.setState({term});
}
onSubmit = async (term) => {
const recName= this.state.term;
term.preventDefault();
const api_key = 'a21e46c6ab81bccebfdfa66f0c4bf5e9';
const api_call = await Axios
.get(`https://www.food2fork.com/api/search?key=${api_key}&q=${recName}&`)
.then(res=> {this.setState({term : res.data.recipes})})
}
render() {
return (
<div>
<form onSubmit={this.onSubmit} >
<div className="search-bar">
<input
type="text"
value={this.state.term}
onChange={event => this.onInputChange(event.target.value)}
/>
<button type="submit">Search</button>
</div>
</form>
<RecipeList List ={this.state.term}/>
</div>
)
}
}
RecipeList Component
const RecipeList = props => (
<div>
{
props.List.map((recipe) => {
return (
<div>{recipe.title}</div>
)
})
}
</div>
)
export default RecipeList;
Thank you guys for your help
Your problem is in this snippet:
onInputChange(term){
this.setState({term});
}
This will set your state variable to a String. For example, if I type in Hello!, your state object will be { term: 'Hello!' }. You're now trying to .map() over a String, String.map is not a function.
The .map function is only available on array.
It looks like data isn't in the format you are expecting it to be (it is {} but you are expecting []).
this.setState({data: data});
should be
this.setState({data: data.conversations});
Check what type "data" is being set to, and make sure that it is an array.
More generally, you can also convert the new data into an array and use something like concat:
var newData = this.state.data.concat([data]);
this.setState({data: newData})
This pattern is actually used in Facebook's ToDo demo app (see the section "An Application") at https://facebook.github.io/react/.
I have a Dashboard component that renders an array of cards with data fetched from a backend server. Users can create additional cards by submitting a form, which then redirects them back to the dashboard page.
My issue is that when the form is submitted, a javascript error 'cannot read property "includes" of undefined' is thrown and the dashboard does not render. If I manually refresh the page, the list renders as expected with the new card. I use Array.includes method to filter the cards based on the filterText state value. Does this error happen because the data has not been fetched when render is called? If so, how can I force the component to wait until there is data before rendering? Please see the components and redux action below.
const CardList = (props) => {
const cards = props.cards.map(({ _id, title}) => {
return (
<Card key={_id} title={title} />
)
});
return (
<div className="container">
<input onChange={ (e) => props.handleChange(e.target.value) } />
<div className="row">
{cards}
</div>
</div>
);
}
export default CardList;
export class Dashboard extends Component {
constructor(props) {
super(props);
this.state = {
filterText: ''
}
}
componentDidMount() {
this.props.fetchCards();
}
handleChange = (filterText) => {
this.setState({filterText});
}
render() {
const cardList = this.props.cards.filter(card =>
card.title.includes(this.state.filterText.trim().toLowerCase())
);
return (
<div>
<CardList cards={cardList}
handleChange={filterText => this.handleChange(filterText)} />
</div>
);
}
};
function mapStateToProps({ cards: { cards }}) {
return {
cards,
}
}
export default connect(mapStateToProps, {fetchCards})(Dashboard);
export class SurveyForm extends Component {
render() {
return (
<div>
<form>
<Field component={CardField} type="text"
label={'title'} name={'title'} key={'title'} />
<Button type="submit" onClick={() => submitCard(formValues, history)}>Next</Button>
</form>
</div>
);
}
}
REDUX ACTION DISPATCHER:
export const submitCard = (values, history) => async dispatch => {
const res = await axios.post('/api/cards', values);
try {
dispatch({ type: SUBMIT_CARD_SUCCESS, payload: res.data });
dispatch({ type: FETCH_USER, payload: res.data })
}
catch(err) {
dispatch({ type: SUBMIT_CARD_ERROR, error: err });
}
history.push('/cards');
}
Similar to what #JasonWarta mentioned, it's worth noting that React does not render anything when false, null, or undefined is returned, so you can usually use && to be more succinct than using the conditional ("ternary") operator:
render() {
return this.props.cards && (
<div>
<CardList
cards={this.props.cards.filter(card => card.title.includes(this.state.filterText.trim().toLowerCase())}
handleChange={filterText => this.handleChange(filterText)}
/>
</div>
);
}
Because && short-circuits, the latter part won't be evaluated so you can avoid TypeErrors, and the component will also render no content (same as when you return null).
I've used ternary operators in this kind of situation. You may need to adjust the check portion of the pattern, depending on what your redux pattern is returning. null value is returned if this.props.cards is falsey.
render() {
return (
{this.props.cards
?
<div>
<CardList
cards={this.props.cards.filter(card => card.title.includes(this.state.filterText.trim().toLowerCase())}
handleChange={filterText => this.handleChange(filterText)}
>
</CardList>
</div>
:
null
}
);
}
As an alternative to other answers you can return something else suitable if there is no data in your render function with an if statement. I prefer moving functions like your filter one outside of render. Maybe one other (better?) approach is doing that filter in your mapStateToProps function.
Also, if I'm not wrong you don't need to pass anything to your handleChange function. Because you are getting filterText back from CardList component then setting your state.
cardList = () => this.props.cards.filter(card =>
card.title.includes(this.state.filterText.trim().toLowerCase()));
render() {
if ( !this.props.cards.length ) {
return <p>No cards</p>
// or return <SpinnerComponent />
}
return (
<div>
<CardList cards={this.cardList()}
handleChange={this.handleChange} />
</div>
);
}