Calling Fetch in React App.js with Prop Drilling - reactjs

So I would like to call fetch from a function (submitURL) in App.js. If I create "componentDidMount()" in App.js and call fetch there, it works, but not from submitURL. I believe this is because submitURL is called via prop drilling. How would I call fetch from submitURL?
App.js
class App extends Component {
state = {
channelURL: '',
videos: []
}
submitURL = (value) => {
fetch('https://jsonplaceholder.typicode.com/todos/1')
.then(response => response.json())
.then(json => console.log(json))
this.setState({
channelURL: value
});
}
render() {
console.log(this.state)
return (
<div className="App">
<h1> Title </h1>
<Channel submitURL={this.submitURL} url={this.state.channelURL}/>
<Videos videos={this.state.videos}/>
</div>
);
}
}
export default App;
Channel.js
class Channel extends Component {
state = {
value: this.props.url
}
handleChange = (e) => {
this.setState({
value: e.target.value
});
}
render() {
return (
<div>
<h1> Enter Channel URL </h1>
<form onSubmit={this.props.submitURL.bind(this, this.state.value)}>
URL: <input type="text" name="url" value={this.state.value} onChange={this.handleChange}/>
<input type="submit" value="Submit" />
</form>
</div>
);
}
}
export default Channel;

submitURL = (value) => {
fetch('https://jsonplaceholder.typicode.com/todos/1')
.then(response => response.json())
.then(json => this.setState({
channelURL: json
}))
}

Related

draft.js: text editor populate value from other component's state

I'm using draft.js to make a text editor and I have two components: CreatePost.js which gets the post fields from the back end and populates the state with the user input and TextEditor.js which contains a text editor which I am using in CreatePost.js. The text editor should populate the body field in the state of CreatePost.js onChange.
My question is how can I get the text editor to populate the state in the other component? Would I need to use props instead?
Before, I had a text area in CreatePost.js which populated the body. I want the text editor in the other component to populate it instead. I've tried using
<TextEditor onChange={this.changeHandler} value={body} /> in CreatePost.js but it didn't work.
console.log(body):
posts.js (controller)
exports.create = (req, res) => {
const { title, body, date } = req.body;
const post = new Post({
title,
body,
date,
"author.id": req.profile._id,
"author.name": req.profile.name,
});
post
.save()
.then((response) => {
res.send(response);
})
.catch((err) => {
return res.status(400).json({
error: errorHandler(err),
});
});
};
CreatePost.js
class CreatePost extends React.Component {
constructor(props) {
super(props);
this.state = {
title: "",
body: "",
createdPost: "",
error: "",
};
}
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,
})
.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);
});
};
...
render() {
const { title, body } = this.state;
return (
<>
<Navbar />
<Tabs>
<TabList className="tabs">
<Tab className="tab">Draft</Tab>
<Tab className="tab">Preview</Tab>
</TabList>
<TabPanel>
<div className="newpost_container">
<form className="newpost_form" onSubmit={this.submitHandler}>
<div className="form-group">
<input
type="text"
placeholder="Title"
name="title"
className="newpost_field newpost_title"
onChange={this.changeHandler}
value={title}
/>
</div>
<div className="form-group newpost_body">
<TextEditor />
</div>
<button className="btn publish-post-btn" type="submit">
Publish
</button>
{this.showSuccess()}
{this.showError()}
</form>
</div>
</TabPanel>
<TabPanel>
<div>
<h1>{title}</h1>
<div>{body}</div>
</div>
</TabPanel>
</Tabs>
</>
);
}
}
export default CreatePost;
TextEditor.js
class TextEditor extends React.Component {
constructor(props) {
super(props);
this.state = {
editorState: EditorState.createEmpty(),
};
this.plugins = [addLinkPlugin];
}
toggleBlockType = (blockType) => {
this.onChange(RichUtils.toggleBlockType(this.state.editorState, blockType));
};
onChange = (editorState) => {
this.setState({
editorState,
});
};
handleKeyCommand = (command) => {
const newState = RichUtils.handleKeyCommand(
this.state.editorState,
command
);
if (newState) {
this.onChange(newState);
return "handled";
}
return "not-handled";
};
// onClick for format options
onAddLink = () => {
const editorState = this.state.editorState;
const selection = editorState.getSelection();
const link = window.prompt("Paste the link -");
if (!link) {
this.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.onChange(RichUtils.toggleLink(newEditorState, selection, entityKey));
};
toggleBlockType = (blockType) => {
this.onChange(RichUtils.toggleBlockType(this.state.editorState, blockType));
};
render() {
return (
<div className="editorContainer">
<div className="toolbar">
<BlockStyleToolbar
editorState={this.state.editorState}
onToggle={this.toggleBlockType}
/>
// format buttons
</div>
<div>
<Editor
placeholder="Post Content"
blockStyleFn={getBlockStyle}
editorState={this.state.editorState}
handleKeyCommand={this.handleKeyCommand}
onChange={this.onChange}
plugins={this.plugins}
placeholder="Post Content"
/>
</div>
</div>
);
}
}
export default TextEditor;
It looks like you've been quite close to solving this actually. You were on the right path when sending down the change handler using props to the TextEditor. One solution to your problem is to move up the editorState to your CreatePost component and then pass the value and the change handler downwards. If you are doing this you should remove the editorState and the change handler for it from the TextEditor file. Just by continuing on your example something like this should work, I haven't tried the code out but it should help you in the right direction.
In CreatePost.js
constructor(props) {
super(props);
this.state = {
title: "",
body: EditorState.createEmpty(),
createdPost: "",
error: "",
};
}
....
<TextEditor onChange={(value) => this.setState({ body: value })} editorState={body} />
In TextEditor.js
<Editor
placeholder="Post Content"
blockStyleFn={getBlockStyle}
editorState={this.props.editorState}
handleKeyCommand={this.handleKeyCommand}
onChange={this.props.onChange}
plugins={this.plugins}
placeholder="Post Content"
/>
When posting the data we need to access the content of the editor instead of the EditorState. We can do this through draft.js API (see more here: https://draftjs.org/docs/api-reference-editor-state/#getcurrentcontent). And that's not enough unfortunately. We also need to to convert the content to a format that's easier to handle. We can do this with draft.js convertToRaw which you also need to import from the library (https://draftjs.org/docs/api-reference-data-conversion/#converttoraw). Convert to raw returns a JS object so we also need to convert that to a string before being able to send it to the server by using JSON.stringify().
axios({
url: `${API}/post/new-post/${_id}`,
method: "POST",
data: {
...this.state,
body: JSON.stringify(convertToRaw(this.state.body.getCurrentContent()))
}
})

Why is my unique ID posts after refresh browser (React, MongoDB, Express, Node)?

I am rather new to React and am making an app with the MERN stack to create, read, update and delete recipes but I'm getting the warning from React that I don't have a unique key for my recipe items. However, when I refresh my browser the warning goes away and my recipe object now has the id. It looks like the recipe ID is not being posted until after the recipe items are re-rendered. I don't get the warning if I pass the index as the key but I am just really wanting to understand why I keep getting this error when trying to use the ID generated from MongoDB.
class RecipeContiner extends Component {
constructor(props) {
super(props);
this.state = {
title: "",
ingredients: "",
summary: "",
recipes: []
}
}
//GET RECIPES
componentDidMount() {
const url = 'http://localhost:5000/recipes/';
axios.get(url)
.then((res) => {
this.setState({ recipes: res.data })
}).catch(err => {
console.log(err);
});
}
onChangeHandler = (e) => {
this.setState({ [e.target.name]:e.target.value})
}
//POST RECIPE
onSubmitHandler = (e) => {
e.preventDefault();
const recipe = {
title: this.state.title,
ingredients: this.state.ingredients,
summary: this.state.summary
}
const url = 'http://localhost:5000/recipes/add';
axios.post(url, recipe)
.then(res => console.log('new recipe!', res.data));
this.setState({
recipes: [...this.state.recipes, recipe],
});
e.target.reset();
}
render() {
return (
<div>
<form onSubmit={this.onSubmitHandler}>
<label>Title:</label>
<input type="text" onChange={this.onChangeHandler} name="title"/>
<label>Ingredients:</label>
<input type="text" onChange={this.onChangeHandler} name="ingredients"/>
<label>Summary:</label>
<input type="text" onChange={this.onChangeHandler} name="summary"/>
<input type="submit" value="Submit" />
</form>
<RecipeList recipes={this.state.recipes} />
<Fab color="primary" aria-label="add">
<AddIcon />
</Fab>
</div>
);
}
//RECIPE LIST COMPONENT
const RecipeList = (props) => {
console.log('props.recipes', props.recipes)
const recipes = props.recipes;
return (
<div>
<ul>
{recipes.map((recipe, index) => (
<RecipeItem
key={recipe._id}
title={recipe.title}
ingredients={recipe.ingredients}
summary={recipe.summary}
/>
))}
</ul>
</div>
);
}
//RECIPE ITEM COMPONENT
const RecipeItem = (props) => {
return (
<li>
<div>{props.title}</div>
<div>{props.ingredients}</div>
<div>{props.summary}</div>
</li>
)
}
}```
[1]: https://i.stack.imgur.com/aZtEO.png
your state don't get the id after you post id. you just add the new recipe from the client and not form the server with the id.
axios.post(url, recipe)
.then(res => this.setState({
recipes: [...this.state.recipes, res.data],
} ,()=>console.log('new recipe!', res.data)));
will do the trick.

How make post request without issues with setState in react?

I have value that i want to update and send an update post request , so i tried this:
EDIT
i add more code that represenet the all component:
import React from 'react'
import axios from 'axios'
import {Card} from 'react-bootstrap'
class SubmitPage extends React.Component{
constructor(props) {
super(props)
this.state={formname:'',
fields:[''],
values:[''],
submissions:0
}
}
handleChange=this.handleChange.bind(this)
saveChanges=this.saveChanges.bind(this)
componentDidMount=this.componentDidMount.bind(this)
//get the Object from DB and initialize new state
componentDidMount()
{
axios.get('http://localhost:2000/form/'+this.props.match.params.id).then(response =>{
this.setState({
formname:response.data.formname,
fields:response.data.fields,
submissions:response.data.submissions
})
})
.catch(function(error){
})
}
handleChange(e,index)
{
this.setState({
values: {
...this.state.values,
[index.index]: e.target.value
}
});
}
async saveChanges(e)
{
e.preventDefault();
//update the values in field array
const {values}=this.state;
const fields=this.state.fields;
Object.keys(values).map(key =>
fields[key].push(values[key])
)
//get submission and update it
axios.get('http://localhost:2000/form/'+this.props.match.params.id)
.then(response =>{
this.setState({
submissions: response.data.submissions
})
console.log(this.state.submissions)
})
.catch(function(error){
console.log(error);
})
//let submissionsUpdate=this.state.submissions;
// submissionsUpdate=submissionsUpdate+1;
this.setState({
submissions: this.state.submissions+1}, ()=> {
const form={
formname:this.state.formname,
fields:this.state.fields,
submissions: this.state.submissions
}
axios.post('http://localhost:2000/form/update/'+this.props.match.params.id,form) //post after sumbmission value update
.then(res => console.log(res.data));
});
window.location='/'
}
render(){
const {fields}=this.state
return(
<div style={{width:'35%' , margin:"0 auto" ,marginTop:"3%"}}>
<Card >
<Card.Header as="h5">{this.state.formname}</Card.Header>
<Card.Body>
<Card.Text>
<div>{fields.length!==0&&fields.map((fields,index)=> {
return (
<div key={fields[0]}>
<div style={{fontSize:'15px' , marginBottom:"-1px"}}>{fields[0]}</div>
<div><input onChange={(e) => this.handleChange(e, {index})} type={fields[1]}></input></div>
</div>
)
})
}
</div>
<button style={{width:100, marginTop:"10px", marginBottom:"20px"}} type="submit" onClick={this.saveChanges} className="btn btn-dark"> Submit</button>
</Card.Text>
</Card.Body>
</Card>
</div>)
}
}
export default SubmitPage
my goal is to increase counter by 1 , but because the setState is asych, something goes wrong , and the result is or increasing by strange number or no change.
what i am doing wrong?

typeError: cannot read property 'users' of null in React

I'm trying to create a change password page in react and i'm getting typeError: cannot read property 'users' of null. The code works for other form pages(where i'm doing PUT and CREATE) but not this one
I tried binding the submit handler to the this keyword but that didn't work.
Also tried binding the handlePasswordChange to the this keyword
./formchange
import React from "react";
import { Link } from "react-router-dom";
var createReactClass = require("create-react-class");
var FormChange = createReactClass({
//setting initial state
getInitialState() {
return {
password: {}
};
},
handlePasswordChange(e) {
this.setState({
password: e.target.value
});
},
handleSubmit(e) {
e.preventDefault();
this.props.onSubmit(this.state);
this.props.history.push("/");
},
render() {
return (
<form
name="categories_post"
className="form-horizontal"
onSubmit={this.handleSubmit}
>
<div id="change_password">
<div className="form-group">
<label
className="col-sm-2 control-label required"
htmlFor="password"
>
Password
</label>
<div className="col-sm-10">
<input
type="text"
value={this.state.password}
onChange={this.handlePasswordChange}
id="password"
className="form-control"
/>
</div>
</div>
<button
type="submit"
id="formChangeSubmit"
className="btn btn-default"
>
Submit
</button>
</div>
</form>
);
}
});
export default FormChange;
./passwordupdate
import React from "react";
import { updateUsers, fetchUsers } from "./actions/appactions";
import FormChange from "./formchange";
var createReactClass = require("create-react-class");
const Update = createReactClass({
getIntitialState() {
return {
users: {}
};
},
componentWillReceiveProps(props) {
this.setState(props);
},
componentDidMount() {
fetchUsers(this.props.match.params.usersId)
.then(data => {
this.setState(state => {
state.users = data;
return state;
});
})
.catch(err => {
console.error("error", err);
});
},
handleSubmit(data) {
updateUsers(this.state.users.id, data);
},
render() {
return (
<div>
<FormChange
onSubmit={this.handleSubmit.bind}
password={this.state.users.password}
/>
</div>
);
}
});
export default Update;
//fetchusers function
export function fetchUsers(id) {
return fetch("https://localhost:44341/api/users/" + id, {
method: "GET",
mode: "cors"
})
.then(res => res.json())
.catch(err => err);
}
<FormChange
onSubmit={this.handleSubmit.bind(this)}
password={this.state.users.password}
/>
make this change and check
I'm not sure but you have data in handleSubmit as parameter but you don't pass it.
try this
You can call function like this:
handleSubmit=(data)=> {
updateUsers(this.state.users.id, data);
},
and call it
onSubmit={(data)=> this.handleSubmit(data)}
The problem was in ComponentDidMount(). The state was always null, had to change it to this
componentDidMount() {
fetchTheUsers(this.props.match.params.usersId)
.then(data => {
this.setState({
users: data
});
})
I did it that way initially because that's how it worked for my other update files. Hope this is useful to someone else.

Search functionality and fetch api in React

I'm working on an app that makes a call to Unsplash's API and displays the response. I was able to get/display the response easily with just the /photos endpoint when I had the fetch request in the componentDidMount(), but I want to make the app searchable, so I added performSearch() with a default query.
But adding performSearch caused this error:
TypeError: cannot read property 'map' of undefined
This is the JSON I'm getting back when I test:
Search endpoint + query
I've tried other solutions I've found on the forums, but so far nothing's fixed the problem. I'm definitely getting back an array, so shouldn't map work?
class App extends Component {
constructor() {
super();
this.state = {
results: [],
loading: true
};
}
componentDidMount() {
this.performSearch();
}
performSearch = (query = 'camping') => {
fetch(`https://api.unsplash.com/search/photos?page=3&query=${query}&client_id=${client_id}`)
.then(response => response.json())
.then(responseData => {
this.setState({
results: responseData.data,
loading: false
});
})
.catch(error => {
console.log('Error fetching and parsing data', error);
});
}
render() {
return (
<div className = "App">
<SearchPhotos onSearch = {this.performSearch} />
<div>
{
(this.state.loading) ? <p>Loading</p> :<PhotoList results={this.state.results} />
}
</div>
</div>
);
}
}
export default App;
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
import React from 'react';
const PhotoList = props =>
<ul>
{props.results.map((result, index) =>
<li key={index}>
<img src={result.urls.small} key={result.id} />
</li>
)}
</ul>;
export default PhotoList;
import React, { Component } from 'react';
class SearchPhotos extends Component {
state = {
searchText: ''
}
onSearchChange = e => {
this.setState({
searchText: e.target.value
});
}
handleSubmit = e => {
e.preventDefault();
this.props.onSearch(this.query.value);
e.currentTarget.reset();
}
render() {
return(
<form className="search-form" onSubmit={this.handleSubmit}>
<input type="search"
onChange={this.onSearchChange}
name="search"
ref={(input) => this.query = input}
placeholder="Search..." />
<button className="search-button" type="submit" id="submit">Go!</button>
</form>
);
}
}
export default SearchPhotos;
performSearch = (query = 'camping') => {
fetch(`https://api.unsplash.com/search/photos?page=3&query=${query}&client_id=${client_id}`)
.then(response => response.json())
.then(responseData => {
this.setState({
results: responseData.results,
loading: false
});
})
.catch(error => {
console.log('Error fetching and parsing data', error);
});
}
responseData.results is the array that your are looking for.

Resources