retrieve text from db using draft.js - reactjs

My draft.js <TextEditor /> populates body with the text e.g: '{"blocks":[{"key":"3mont","text":"lorem ipsum","type":"unstyled","depth":0,"inlineStyleRanges":[],"entityRanges":[],"data":{}}],"entityMap":{}}'.
In Post.js, I want to retrieve and display the formatted text from the db. I've read that in order to do this, I must use convertToRaw() and then convertFromRaw() when retrieving it from the db but I'm having the same problems as this (I'm receiving the cors error and Unexpected token u in JSON at position 0) whenever I use convertFromRaw() and try to retrieve the formatted text from the db.
I have setup my server to support cors so is it because I am trying to parse an invalid response into JSON? Why am I getting the cors error and how can I solve it?
GitHub
How can I get the formatted text from the db in Post.js? Any help would be really appreciated!
CreatePost.js
class CreatePost extends React.Component {
constructor(props) {
super(props);
this.state = {
title: "",
body: EditorState.createEmpty(),
};
}
changeHandler = (e) => {
this.setState({ [e.target.name]: e.target.value });
};
submitHandler = (e) => {
e.preventDefault();
const {
user: { _id },
} = isAuthenticated();
axios({
url: `${API}/post/new-post/${_id}`,
method: "POST",
data: {
...this.state,
body: JSON.stringify(convertToRaw(this.state.body.getCurrentContent())),
},
})
.then((response) => {
// this.setState({ createdPost: this.state.title });
return response
})
.catch((error) => {
if (!this.state.title || !this.state.body) {
this.setState({
error: "This post must contain a title and a body.",
});
}
console.log(error);
});
};
// Attempt to map through blocks
//getText = () => {
// const {body} = this.state;
//const arr = body.blocks.map(({ text }) => text).join(' ')
// console.log(arr)
//}
render() {
const { title, body } = this.state;
return (
<>
<Navbar />
<Tabs>
<TabList>
<Tab>Draft</Tab>
<Tab>Preview</Tab>
</TabList>
<TabPanel>
<div>
<form onSubmit={this.submitHandler}>
<div>
// title input
</div>
<div>
<TextEditor
onChange={(value) => this.setState({ body: value })}
editorState={body}
/>
</div>
<button type="submit">
Publish
</button>
</form>
</div>
</TabPanel>
<TabPanel>
<div>
<h1>{title}</h1>
// display body text value here too
{this.getText()}
</div>
</TabPanel>
</Tabs>
</>
);
}
}
Post.js (display body text)
const [post, setPost] = useState({});
const [error, setError] = useState(false);
const id = props.match.params.id;
const loadSinglePost = (slug, id) => {
read(slug, id).then((data) => {
if (error) {
console.log(data.error);
setError(data.error);
} else {
setPost(data)
console.log(data);
}
});
};
useEffect(() => {
const slug = props.match.params.slug;
loadSinglePost(slug, id);
}, [props]);
return (
<>
<div>
<h3>{post.title}</h3>
...
// display text value below
<p>{post.body}</p>
</div>
</div>
</>
);
};
TextEditor.js
class TextEditor extends React.Component {
constructor(props) {
super(props);
this.plugins = [addLinkPlugin];
}
toggleBlockType = (blockType) => {
this.props.onChange(RichUtils.toggleBlockType(this.props.editorState, blockType));
};
handleKeyCommand = (command) => {
const newState = RichUtils.handleKeyCommand(
this.props.editorState,
command
);
if (newState) {
this.props.onChange(newState);
return "handled";
}
return "not-handled";
};
onUnderlineClick = () => {
this.props.onChange(
RichUtils.toggleInlineStyle(this.props.editorState, "UNDERLINE")
);
};
onBoldClick = (event) => {
this.props.onChange(RichUtils.toggleInlineStyle(this.props.editorState, "BOLD"));
};
onItalicClick = () => {
this.props.onChange(
RichUtils.toggleInlineStyle(this.props.editorState, "ITALIC")
);
};
onAddLink = () => {
const editorState = this.props.editorState;
const selection = editorState.getSelection();
const link = window.prompt("Paste the link -");
if (!link) {
this.props.onChange(RichUtils.toggleLink(editorState, selection, null));
return "handled";
}
const content = editorState.getCurrentContent();
const contentWithEntity = content.createEntity("LINK", "MUTABLE", {
url: link,
});
const newEditorState = EditorState.push(
editorState,
contentWithEntity,
"create-entity"
);
const entityKey = contentWithEntity.getLastCreatedEntityKey();
this.props.onChange(RichUtils.toggleLink(newEditorState, selection, entityKey));
};
toggleBlockType = (blockType) => {
this.props.onChange(RichUtils.toggleBlockType(this.props.editorState, blockType));
};
render() {
return (
<div>
// formatting buttons
<div>
<Editor
blockStyleFn={getBlockStyle}
editorState={this.props.editorState}
handleKeyCommand={this.handleKeyCommand}
onChange={this.props.onChange}
plugins={this.plugins}
placeholder="Post Content"
/>
</div>
</div>
);
}
}

Related

draft.js - retrieve formatted text from db

My draft.js <TextEditor /> populates body with the text e.g: '{"blocks":[{"key":"3mont","text":"lorem ipsum","type":"unstyled","depth":0,"inlineStyleRanges":[],"entityRanges":[],"data":{}}],"entityMap":{}}' and persists it to the db after using convertToRaw().
In Post.js, I want to retrieve and display the formatted text from the db.
I've read that in order to do this, I must use convertToRaw() and then convertFromRaw() when retrieving it from the db but I'm having the same problems as this (I'm receiving the cors error and Unexpected token u in JSON at position 0) whenever I use convertFromRaw() and try to retrieve the formatted text from the db.
I've set up my server to support cors so why am I receiving the cors error? Is it because I am trying to parse an invalid response into JSON?
How can I get the formatted text from the db in Post.js?
Any help would be really appreciated!
GitHub
CreatePost.js
class CreatePost extends React.Component {
constructor(props) {
super(props);
this.state = {
title: "",
body: EditorState.createEmpty(),
};
}
changeHandler = (e) => {
this.setState({ [e.target.name]: e.target.value });
};
submitHandler = (e) => {
e.preventDefault();
const {
user: { _id },
} = isAuthenticated();
axios({
url: `${API}/post/new-post/${_id}`,
method: "POST",
data: {
...this.state,
body: JSON.stringify(convertToRaw(this.state.body.getCurrentContent())),
},
})
.then((response) => {
// this.setState({ createdPost: this.state.title });
return response
})
.catch((error) => {
if (!this.state.title || !this.state.body) {
this.setState({
error: "This post must contain a title and a body.",
});
}
console.log(error);
});
};
// Attempt to map through blocks
//getText = () => {
// const {body} = this.state;
//const arr = body.blocks.map(({ text }) => text).join(' ')
// console.log(arr)
//}
render() {
const { title, body } = this.state;
return (
<>
<Navbar />
<Tabs>
<TabList>
<Tab>Draft</Tab>
<Tab>Preview</Tab>
</TabList>
<TabPanel>
<div>
<form onSubmit={this.submitHandler}>
<div>
// title input
</div>
<div>
<TextEditor
onChange={(value) => this.setState({ body: value })}
editorState={body}
/>
</div>
<button type="submit">
Publish
</button>
</form>
</div>
</TabPanel>
<TabPanel>
<div>
<h1>{title}</h1>
// display body text value here too
{this.getText()}
</div>
</TabPanel>
</Tabs>
</>
);
}
}
Post.js (display body text)
const [post, setPost] = useState({});
const [error, setError] = useState(false);
const id = props.match.params.id;
const loadSinglePost = (slug, id) => {
read(slug, id).then((data) => {
if (error) {
console.log(data.error);
setError(data.error);
} else {
setPost(data)
console.log(data);
}
});
};
useEffect(() => {
const slug = props.match.params.slug;
loadSinglePost(slug, id);
}, [props]);
return (
<>
<div>
<h3>{post.title}</h3>
...
// display text value below
<p>{post.body}</p>
</div>
</div>
</>
);
};
TextEditor.js
class TextEditor extends React.Component {
constructor(props) {
super(props);
this.plugins = [addLinkPlugin];
}
toggleBlockType = (blockType) => {
this.props.onChange(RichUtils.toggleBlockType(this.props.editorState, blockType));
};
handleKeyCommand = (command) => {
const newState = RichUtils.handleKeyCommand(
this.props.editorState,
command
);
if (newState) {
this.props.onChange(newState);
return "handled";
}
return "not-handled";
};
onUnderlineClick = () => {
this.props.onChange(
RichUtils.toggleInlineStyle(this.props.editorState, "UNDERLINE")
);
};
onBoldClick = (event) => {
this.props.onChange(RichUtils.toggleInlineStyle(this.props.editorState, "BOLD"));
};
onItalicClick = () => {
this.props.onChange(
RichUtils.toggleInlineStyle(this.props.editorState, "ITALIC")
);
};
onAddLink = () => {
const editorState = this.props.editorState;
const selection = editorState.getSelection();
const link = window.prompt("Paste the link -");
if (!link) {
this.props.onChange(RichUtils.toggleLink(editorState, selection, null));
return "handled";
}
const content = editorState.getCurrentContent();
const contentWithEntity = content.createEntity("LINK", "MUTABLE", {
url: link,
});
const newEditorState = EditorState.push(
editorState,
contentWithEntity,
"create-entity"
);
const entityKey = contentWithEntity.getLastCreatedEntityKey();
this.props.onChange(RichUtils.toggleLink(newEditorState, selection, entityKey));
};
toggleBlockType = (blockType) => {
this.props.onChange(RichUtils.toggleBlockType(this.props.editorState, blockType));
};
render() {
return (
<div>
// formatting buttons
<div>
<Editor
blockStyleFn={getBlockStyle}
editorState={this.props.editorState}
handleKeyCommand={this.handleKeyCommand}
onChange={this.props.onChange}
plugins={this.plugins}
placeholder="Post Content"
/>
</div>
</div>
);
}
}
Apparently draft-js does not have html output function because it's supposed to have no assumption on the output so people can tune their output however they want (see this). This means we'll have to implement it ourselves and if you're looking for just an html or markdown output to preserve in the database, then this mono repo can be of help. I've implemented an example of how to do it in this sandbox. Note that I used dangerouslySetInnerHTML for demonstration which is not optimal. You may want to use sanitization and rich text components to display back the posts. As a matter of fact I'd suggest ditching html and going for markdown instead if possible.

Api data returns to 0 after information is called (React)

I am fetching information about stocks in componentDidUpdate in React. The information that I want to fetch renders but after a few seconds the information reverts back to 0. I don't know why it is happening and I have tried to fetch it in componentDidMount first but the same thing keeps happening. I don't know how to go about solving this problem. Can someone point me in the right direction?
constructor(props) {
super(props);
this.state = {
userInput: '',
stockSymbol: [],
marketData: [],
isLoaded: false,
symbol1: [],
};
}
typeSymbol = e => {
this.setState({
userInput: e.target.value.toUpperCase(),
});
};
componentDidMount() {
const { userInput } = this.state;
const urls = [
`https://api.iextrading.com/1.0/ref-data/symbols`,
`https://api.iextrading.com/1.0/tops`,
];
let requests = urls.map(url => fetch(url));
Promise.all(requests)
.then(responses => {
return Promise.all(responses.map(response => response.json()));
})
.then(responses => {
this.setState({
stockSymbol: responses[0],
marketData: responses[1],
});
});
}
componentDidUpdate(prevProps, prevState, snapShot) {
const { userInput, symbol1 } = this.state;
if (prevState.userInput !== userInput) {
fetch(
`https://finnhub.io/api/v1/quote?symbol=${userInput}&token=brjo6knrh5r9g3ot7150`,
)
.then(res => res.json())
.then(responses => this.setState({ symbol1: responses }));
}
}
render() {
const { stockSymbol, userInput, marketData, symbol1 } = this.state;
const filteredSymbols = stockSymbol.filter(sym => sym.symbol === userInput);
const foundMarket = marketData.find(market => market.symbol === userInput);
const objData = () => {
for (const stockData in symbol1) {
console.log(`${stockData}: ${symbol1[stockData]}`);
}
};
objData();
const clock = new Date().toLocaleString();
return (
<div className="enterstock">
<div className="fields">
<span className="clock">{clock}</span>
<h1 className="title">Enter Stock Symbol</h1>
<input
type="text"
className="symfields"
name="symbolname"
onChange={this.typeSymbol}
/>
</div>
{filteredSymbols.map((stock, i) => {
return (
<div className="stockings">
<div className="named">
<h2 className="symbol">{this.state.userInput}</h2>
<h2 className=" name" key={i}>
{stock.name}
</h2>
</div>
{
<h2 className="stocked price" key={i}>
Price: {symbol1.c}
</h2>
}
</div>
);
})}
,
</div>
);
}

Detect when Firebase sign in has finished or canceled in this scenario

I learn ReactJs and now I must have Firebase signin. I have a design question on how to detect when Firebase linkWithPopup finish. User press Button and my anonymous user Firebase UID will be turned into a Google credential one.
The linkWithPopup pops up and user select one Google account to use.
I must detect when this process finish or aborted.
Here is my code:
This method get's called when user click Button for Google signin:
onSocialLoginLink = provider => {
const { firebase, changeUserRole } = this.props;
firebase.auth.currentUser
.linkWithPopup(firebase[provider])
// .linkWithRedirect(this.props.firebase[provider])
.then(changeUserRole())
.then(this.fetchSignInMethods)
.catch(error => this.setState({ error }));
};
The problem I encounter is that changeUserRole() gets called before linkWithPopup returns since linkWithPopup of course run asynchronous. This means that my user get this new roll from changeUserRole() even if User select to abort signin.
I must detect when this process finish or aborted.
What would be a recommended best way to do this?
My ide is that if I can detect maybe when signin window looses focus and regain focus I could look at if Firebase user have changed provider to Google or is still anonymous user? Is this doable?
This is the Component that handle signin:
/* eslint-disable max-classes-per-file */
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { compose } from 'recompose';
import { AuthUserContext, withAuthorization } from '../../session';
import { withFirebase } from '../../firebase';
import { SIGN_IN_METHODS } from '../../constants/signinmethods';
import * as ROLES from '../../constants/roles';
import '../../styles/link-account.scss';
import { changeToUserRole } from '../../redux/userData/user.actions';
class LoginManagementBase extends Component {
constructor() {
super();
this.state = {
activeSignInMethods: [],
anonymousSignIn: null,
error: null,
};
}
componentDidMount() {
this.fetchSignInMethods();
}
fetchSignInMethods = () => {
const { firebase, authUser } = this.props;
const email = authUser.email === null ? 'none#guest.ac' : authUser.email;
firebase.auth
.fetchSignInMethodsForEmail(email)
.then(activeSignInMethods =>
this.setState({
activeSignInMethods,
anonymousSignIn: activeSignInMethods.length === 0,
error: null,
}),
)
.catch(error => this.setState({ error }));
};
onSocialLoginLink = provider => {
const { firebase, changeUserRole } = this.props;
firebase.auth.currentUser
.linkWithPopup(firebase[provider])
// .linkWithRedirect(this.props.firebase[provider])
.then(changeUserRole())
.then(this.fetchSignInMethods)
.catch(error => this.setState({ error }));
};
onDefaultLoginLink = password => {
const { firebase, authUser } = this.props;
const credential = firebase.emailAuthProvider.credential(authUser.email, password);
firebase.auth.currentUser
.linkAndRetrieveDataWithCredential(credential)
.then(this.fetchSignInMethods)
.catch(error => this.setState({ error }));
};
onUnlink = providerId => {
const { firebase } = this.props;
firebase.auth.currentUser
.unlink(providerId)
.then(this.fetchSignInMethods)
.catch(error => this.setState({ error }));
};
render() {
const { activeSignInMethods, error } = this.state;
const { saveRolesErr, isSavingRole } = this.props;
// if (isSavingRole) return null;
return (
<div className="provideToggler">
<h1>
You are signed in Anonymously!
<br />
Changes you do is only saved in this browser.
<br /> If you want to access your progress anywhere please sign in below!
</h1>
<ul>
{SIGN_IN_METHODS.map(signInMethod => {
const onlyOneLeft = activeSignInMethods.length === 1;
const isEnabled = activeSignInMethods.includes(signInMethod.id);
return (
<li key={signInMethod.id}>
{signInMethod.id === 'password' ? (
<DefaultLoginToggle
// accountEmail={this.props.authUser.email}
onlyOneLeft={onlyOneLeft}
isEnabled={isEnabled}
signInMethod={signInMethod}
onLink={this.onDefaultLoginLink}
onUnlink={this.onUnlink}
/>
) : (
<SocialLoginToggle
onlyOneLeft={onlyOneLeft}
isEnabled={isEnabled}
signInMethod={signInMethod}
onLink={this.onSocialLoginLink}
onUnlink={this.onUnlink}
/>
)}
</li>
);
})}
</ul>
<h1 style={{ color: 'red' }}>
{error && error.message}
{saveRolesErr && saveRolesErr.message}
</h1>
</div>
);
}
}
const SocialLoginToggle = ({ onlyOneLeft, isEnabled, signInMethod, onLink, onUnlink }) =>
isEnabled ? (
<button type="button" onClick={() => onUnlink(signInMethod.id)} disabled={onlyOneLeft}>
Unlink <i className={signInMethod.icon} aria-hidden="true" /> {signInMethod.name} sign in
</button>
) : (
<button type="button" onClick={() => onLink(signInMethod.provider)}>
Link <i className={signInMethod.icon} aria-hidden="true" /> {signInMethod.name} sign in
</button>
);
// TODO This is not in use but might use it later
class DefaultLoginToggle extends Component {
constructor() {
super();
this.state = { passwordOne: '', passwordTwo: '' };
}
onSubmit = event => {
const { passwordOne } = this.state;
const { onLink } = this.props;
event.preventDefault();
onLink(passwordOne);
this.setState({ passwordOne: '', passwordTwo: '' });
};
onChange = event => {
this.setState({ [event.target.name]: event.target.value });
};
render() {
const { signInMethod } = this.props;
const { passwordOne, passwordTwo } = this.state;
const isInvalid = passwordOne !== passwordTwo || passwordOne === '';
return (
<form onSubmit={this.onSubmit}>
Link <i className={signInMethod.icon} aria-hidden="true" /> {signInMethod.name} sign in
<input
name="passwordOne"
value={passwordOne}
onChange={this.onChange}
type="password"
placeholder="Password for email sign in"
/>
<input
name="passwordTwo"
value={passwordTwo}
onChange={this.onChange}
type="password"
placeholder="Confirm New Password"
/>
<button disabled={isInvalid} type="submit">
Save password for email sign in
</button>
</form>
);
}
}
const mapDispatchToProps = dispatch => ({
changeUserRole: () => dispatch(changeToUserRole()),
});
const mapStateToProps = state => {
return {
isSavingRole: state.user.isSavingRoles,
saveRolesErr: state.user.saveRolesErrMsg,
};
};
const enhance = compose(withFirebase, connect(mapStateToProps, mapDispatchToProps));
const LoginManagement = enhance(LoginManagementBase);
const LinkAccounts = () => (
<AuthUserContext.Consumer>
{authUser => (
<div>
<LoginManagement authUser={authUser} />
</div>
)}
</AuthUserContext.Consumer>
);
const condition = authUser => authUser && authUser.roles.includes(ROLES.ANON);
export default withAuthorization(condition)(LinkAccounts);
if that's the problem you can add a condition to check than change the role
const { firebase, changeUserRole } = this.props;
firebase.auth.currentUser
.linkWithPopup(firebase[provider])
.then(res=>{
if(res.credential){changeUserRole()}
})
.then(this.fetchSignInMethods)
.catch(error => this.setState({ error }));
};```

React context: send input data to another component

I have 3 components:
Search.js, Customers.js and Customer.js
In Search.js I have an input field. I want to send whatever value entered in the field over to the Customer.js component. I thought this would be straightforward, but I was wrong ...
I have also a context.js component that stores state for the application (I don't want to use redux because I don't know it yet).
Sorry but this is gonna be a long post as I want to give the background for this specific situation:
context.js
const Context = React.createContext();
const reducer = (state, action) => {
switch (action.type) {
case "SEARCH_CUSTOMERS":
return {
...state,
customer_list: action.payload,
firstName: ''
};
default:
return state;
}
};
export class Provider extends Component {
state = {
customer_list: [],
firstName: "",
dispatch: action => this.setState(state => reducer(state, action))
};
componentDidMount() {
axios
.get("/api")
.then(res => {
console.log(res.data);
this.setState({ customer_list: res.data });
})
.catch(error => console.log(error));
}
render() {
return (
<Context.Provider value={this.state}>
{this.props.children}
</Context.Provider>
);
}
}
export const Consumer = Context.Consumer;
Search.js: the input value I want to send to Customer is 'firstName'
class Search extends Component {
state = {
firstName: ""
};
onChange = e => {
this.setState({ [e.target.name]: e.target.value });
};
findCustomer = (dispatch, e) => {
e.preventDefault();
axios
.get("/api/customers", {
params: {
firstName: this.state.firstName,
}
})
.then(res => {
dispatch({
type: "SEARCH_CUSTOMERS",
payload: res.data
});
this.setState({ firstName: "" });
});
};
return (
<Consumer>
{value => {
const { dispatch } = value;
return (
<form onSubmit={this.findCustomer.bind(this, dispatch)}>
<div className="form-group">
<input
ref={input => {
this.nameInput = input;
}}
type="text"
name="firstName"
value={this.state.firstName}
onChange={this.onChange}
/>
the Customers.js:
class Customers extends Component {
render() {
const key = Date.now();
return (
<Consumer>
{value => {
const { customer_list} = value;
if (customer_list === undefined || customer_list.length === 0) {
return <Spinner />;
} else {
return (
<React.Fragment>
<h3 className="text-center mb-4">{heading}</h3>
<div className="row">
{customer_list.map(item => (
<Customer key={item.key} customer={item} />
))}
</div>
</React.Fragment>
);
}
}}
</Consumer>
);
}
}
export default Customers;
and Finally theCustomer.js: this is where I want the input value to be displayed:
const Customer = props => {
const { customer } = props;
return (
<div className="col-md-12">
<div className="card-body">
<strong>{customer.firstName}</strong> // not working
...
}
the {customer.firstName} does not show the value.
Is is necessary to go through the intermediate Customers.js component to pass the input value?
I would like to keep the architecture as is (with the context.js) and display the value in the Customer.js component.

How to setState to answer from APi and use map

Im trying to create recipes searcher. In App.js I receive query from search input from another component and I want to setState to answer from APi. Console.log from callback in setState shows updated state but the state is not updated. I need setState updaed so I can use map on it and display list of recipes in render. It gives me error map is not a function because this.state.recipesList is still empty. Anyone can help me ?
class App extends Component {
state = {
query: "",
recipesList: []
};
getQuery = query => {
const key = "2889f0d3f51281eea62fa6726e16991e";
const URL = `https://www.food2fork.com/api/search?key=${key}&q=${query}`;
fetch(URL)
.then(res => res.json())
.then(res => {
this.setState(
{
recipesList: res
},
() => {
console.log(this.state.recipesList);
}
);
});
console.log(this.state.recipesList);
};
render() {
const test = this.state.recipesList.map(item => {
return (
<div className="recispesList">
<h1>{item.title}</h1>
</div>
);
});
return (
<div className="App">
<Search query={this.getQuery} />
<div className="contentWrapper">{}</div>
</div>
);
}
}
Search component:
class Search extends Component {
state = {
searchValue: ""
};
handleChange = val => {
let searchValue = val.target.value;
this.setState({
searchValue
});
};
handleSubmit = e => {
e.preventDefault();
this.setState({
searchValue: ""
});
this.props.query(this.state.searchValue);
};
render() {
return (
<div className="searchWrapper">
<form onSubmit={this.handleSubmit}>
<input onChange={this.handleChange} value={this.state.searchValue} />
<button />
</form>
</div>
);
}
}
export default Search;
It seems that instead of directly assigning the whole response to recipesList:
this.setState(
{
recipesList: res
},
() => {
console.log(this.state.recipesList);
}
);
you need to get recipes array first via res.recipes:
this.setState(
{
recipesList: res.recipes
},
() => {
console.log(this.state.recipesList);
}
);

Resources