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.
Related
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.
Iam new to React and I'm trying to interact with the swapi API.
I want to get the list of films (movie titles list) and when I click on a title to show the opening_crawl from the json object.
I managed to get the film titles in an array. I don't know how to proceed from here.
Here is my code:
class StarWarsApp extends React.Component {
render() {
const title = "Star Wars";
const subtitle = "Movies";
return (
<div>
<Header title={title} />
<Movies />
</div>
);
}
}
class Header extends React.Component {
render() {
return (
<div>
<h1>{this.props.title}</h1>
</div>
);
}
}
class Movies extends React.Component {
constructor(props) {
super(props);
this.handleMovies = this.handleMovies.bind(this);
this.state = {
movies: []
};
this.handleMovies();
}
handleMovies() {
fetch("https://swapi.co/api/films")
.then(results => {
return results.json();
})
.then(data => {
console.log(data);
let movies = data.results.map(movie => {
return <div key={movie.episode_id}>{movie.title}</div>;
});
this.setState(() => {
return {
movies: movies
};
});
});
}
render() {
return (
<div>
<h1>Episodes</h1>
<div>{this.state.movies}</div>
</div>
);
}
}
ReactDOM.render(<StarWarsApp />, document.getElementById("app"));
To iterate over movies add this in render method:
render(){
return (
<div>
<h1>Episodes</h1>
{
this.state.movies.map((movie, i) => {
return (
<div className="movie" onClick={this.handleClick} key={i}>{movie.title}
<div className="opening">{movie.opening_crawl}</div>
</div>
);
})
}
</div>
);
}
Add this method to your Movies component to add active class on click to DIV with "movie" className:
handleClick = event => {
event.currentTarget.classList.toggle('active');
}
Include this css to your project:
.movie .opening {
display: none;
}
.active .opening {
display: block
}
After fetching the data, just keep it in your state then use the pieces in your components or JSX. Don't return some JSX from your handleMovies method, just do the setState part there. Also, I suggest using a life-cycle method (or hooks API maybe if you use a functional component) to trigger the fetching. By the way, don't use class components unless you need a state or life-cycle methods.
After that, you can render your titles in your render method by mapping the movies state. Also, you can have a place for your opening_crawls part and render it with a conditional operator. This condition changes with a click. To do that you have an extra state property and keep the movie ids there. With the click, you can set the id value to true and show the crawls.
Here is a simple working example.
const StarWarsApp = () => {
const title = "Star Wars";
const subtitle = "Movies";
return (
<div>
<Header title={title} />
<Movies />
</div>
);
}
const Header = ({ title }) => (
<div>
<h1>{title}</h1>
</div>
);
class Movies extends React.Component {
state = {
movies: [],
showCrawl: {}
};
componentDidMount() {
this.handleMovies();
}
handleMovies = () =>
fetch("https://swapi.co/api/films")
.then(results => results.json())
.then(data => this.setState({ movies: data.results }));
handleCrawl = e => {
const { id } = e.target;
this.setState(current => ({
showCrawl: { ...current.showCrawl, [id]: !current.showCrawl[id] }
}));
};
render() {
return (
<div>
<h1>Episodes</h1>
<div>
{this.state.movies.map(movie => (
<div
key={movie.episode_id}
id={movie.episode_id}
onClick={this.handleCrawl}
>
{movie.title}
{this.state.showCrawl[movie.episode_id] && (
<div style={{ border: "1px black solid" }}>
{movie.opening_crawl}
</div>
)}
</div>
))}
</div>
</div>
);
}
}
ReactDOM.render(<StarWarsApp />, document.getElementById("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>
I am using id on the target div to get it back from the event object. I don't like this method too much but for the sake of clarity, I used this. You can refactor it and create another component may be, then you can pass the epoisde_id there and handle the setState part. Or you can use a data attribute instead of id.
I have to say I started Javascript and React this week so I am not really familiar with it yet or with anything in the front end.
I have a link button in side a toolbar. I want to be able to click it, opening a text box where I can write a link, and then the text is hypertexted with it. Just want to say that any tip is appreciated.
Something like the following pictures.
I have coded the toolbar already and am using the slate-react module for the Editor (the text editor used). I am trying to follow what was done in a GitHub example, which is not exactly the same.
So, in essence, it is a link component inside a toolbar, which is inside a "Tooltip" component (that contains the horizontal toolbar plus another vertical bar), which is inside the editor.
My question is: How do I use react and slate editor to tie the Links together in the toolbar? Does the Link component need a state and onChange function? How can I include the Link component in the toolbar (button group), alongside the other buttons within "const Marks"?
I get that these questions might be basic but I am a beginner and would appreciate explanation.
My created Link component can wrap and unwrap link. When clicked,
onClickLink = event => {
event.preventDefault()
const { value } = this.state
const hasLinks = this.hasLinks()
const change = value.change()
if (hasLinks) {
change.call(this.unwrapLink)
}
else
{
const href = window.prompt('Enter the URL of the link:')
change.call(this.wrapLink, href)
}
this.onChange(change)
}
The wrap, unwrap and hasLinks boolean
class Links extends React.Component {
onChange = ({ value }) => {
this.setState({ value })
}
wrapLink(change, href) {
change.wrapInline({
type: 'link',
data: { href },
})
change.moveToEnd() }
unwrapLink(change) {
change.unwrapInline('link') }
hasLinks = () => {
const { value } = this.state
return value.inlines.some(inline => inline.type == 'link')
}
To render it in the editor.
const renderNode = ({ children, node, attributes }) => {
switch (node.type) {
case 'link': {
const { data } = node
const href = data.get('href')
return (
<a {...attributes} href={href}>
{children}
</a>
)
}
The "Tooltip" component, holding MarkSelect (the horizontal toolbar like the one in the picures) and another vertical bar called NodeSelector.
function Tooltip({ onChange, value }: Props) {
return (
<Fragment>
<SelectionPlacement
value={value}
render={({ placement: { left, top, isActive } }) => (
<div
id=...
{
isActive,
},
)}
style={{ left, top }}
>
<NodeSelector onChange={onChange} value={value} />
<MarkSelector onChange={onChange} value={value} />
</div>
)}
/>
The MarkSelector and other Marks (buttons) in the button group.
const MarkSelector = function MarkSelector({ onChange, value }: Props) {
return (
<ButtonGroup className=...>
{Marks.map(({ tooltip, text, type }) => {
const isActive = value.activeMarks.some(mark => mark.type === type);
return (
<Tooltip key={type} title={tooltip}>
<Button
className={classNames({ 'secondary-color': isActive })}
onMouseDown={event => {
event.preventDefault();
const change = value.change().toggleMark(type);
onChange(change);
}}
size=...
style=...
}}
>
{text}
</Button>
</Tooltip>
);
})}
</ButtonGroup>
);
};
const Marks = [
{
type: BOLD,
text: <strong>B</strong>,
tooltip: (
<strong>
Bold
<div className=...</div>
</strong>
),
},
{
type: ITALIC,
text:...
The editor with the tooltip.
render() {
const { onChangeHandler, onKeyDown, value, readOnly } = this.props;
return (
<div
className=...
id=...
style=..
>
{!readOnly && (
<EditorTooltip value={value} onChange={onChangeHandler} />
)}
<SlateEditor
ref=...
className=...
placeholder=...
value={value}
plugins={plugins}
onChange={onChangeHandler}
onKeyDown={onKeyDown}
renderNode={renderNode}
renderMark={renderMark}
readOnly={readOnly}
/>
{!readOnly && <ClickablePadding onClick={this.focusAtEnd} grow />}
</div>
);
}
Although it is not a recommended way to manipulate DOM directly in data-driven frontend frameworks, but you could always get the HTML element of the link, and set its innerHTML (which is the hypertext) according to internal states. This is hacky but it might works.
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>
);
}
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}
/>