Access variable modified in function in render method - javascript

I have the following code (trimmed the form and some other boilerplate):
import React, { Component } from 'react';
import Modal from 'react-modal';
var responseMessages;
export default class ContactForm extends Component {
handleSubmit(event) {
responseMessages = []
fetch('http://127.0.0.1:4000/test', {
method: 'POST',
mode: 'cors',
headers: {
"Access-Control-Allow-Origin":"*",
'Content-Type': 'application/json'
},
body: JSON.stringify(data)
}).then((response) => response.json())
.then((responseJson) => {
for(var i = 0; i < responseJson.errors.length; i++) {
responseMessages.push(
<p>{responseJson.errors[i].msg}</p>
);
}
})
.then(this.openModal());
}
render() {
return (
<div>
<Modal isOpen={this.state.modalIsOpen}
onRequestClose={this.closeModal}
ariaHideApp={false}
style={customStyles}>
<div>
{responseMessages}
</div>
</Modal>
</div>
)}}
Adding {responseMessages} in the Modal displays nothing, but if I change it to {console.log(responseMessages)} it shows in the console that responseMessages is not empty (it has different length, but not empty)
What could be the reason for that?
EDIT: openModal function:
openModal() {
this.setState({modalIsOpen: true});
}
ResponseJson:
{"errors":[{"location":"body","param":"message","msg":"error message","value":"entered value"}]}

This is a javascript issue and has nothing to do with react.
when you write .then(this.openModal()) the openModal function will be called immediately. So the actual code should be
.then(this.openModal.bind(this));
or using arrow function
or .then(() => this.openModal());
if you are using some autobind decorator or if you are binding functions inside constructor then simply .then(this.openModal); should also work.

Related

Need help to display data from external API

I fetched some data from an API, Im trying to display the data but I'm doing something wrong. Can someone help? I have attached a photos of the fetched data on the console and my code[![data api
import React, {useState, useEffect} from 'react'
import './Track.css';
export default function Track() {
const [carbon] = useState([])
useEffect( () => {
const headers = {
'Accept':'application/json'
};
fetch('https://api.carbonintensity.org.uk/intensity',
{
method: 'GET',
headers: headers
})
.then(function(res) {
return res.json();
}).then(function(body) {
console.log(body);
});
})
return (
<div>
<p>Track</p>
<div>
<p>{carbon.forecast}</p>
</div>
</div>
)
}
]1]1
Change to
import React, { useState, useEffect } from 'react'
import './Track.css';
export default function Track() {
const [carbon, setCarbon] = useState([])
useEffect(() => {
const headers = {
'Accept': 'application/json'
};
fetch('https://api.carbonintensity.org.uk/intensity',
{
method: 'GET',
headers: headers
})
.then(function (res) {
setCarbon(res.data)
}).then(function (body) {
console.log(body);
});
})
return (
<div>
<div>
{carbon.map((obj, i) => (
<li key={i}>
<ul>{obj.from}</ul>
</li>
))}
</div>
</div>
)
}
I recommend to you study https://developer.mozilla.org/pt-BR/docs/Web/JavaScript/Reference/Global_Objects/Array/map
you forgot some little thing:
first you forgot the setCarbon in the useStae hook you will need it to pass the response from the fetch.
You forgot to set the state in the fecth.
You will need to add a condition to render only when the state (carbon) is set.
you need to add an empty dependency to useEffect
import React, { useState, useEffect } from "react";
export default function Track() {
const [carbon, setCarbon] = useState([]);
useEffect(() => {
const headers = {
Accept: "application/json"
};
fetch("https://api.carbonintensity.org.uk/intensity", {
method: "GET",
headers: headers
})
.then((res) => {
return res.json();
})
.then((body) => {
console.log(body.data);
setCarbon(body.data);
});
}, []);
return (
<div>
<p>Track</p>
{carbon.length > 0 && (
<div>
{carbon.map((c, i) => (
<p key={i}>
<div>from: {c.from} </div>
<div>to: {c.to}</div>
<div>forecast: {c.intensity.forecast}</div>
<div>actual: {c.intensity.actual}</div>
<div>index: {c.intensity.index}</div>
</p>
))}
</div>
)}
</div>
);
}
Here you go,
Remember, state is like a place to store data for your component.
When you use fetch, you are getting data and now you need to save it to your state.
If you use state inside of your JSX, you can get the information to display.
Check out the console log, to look at the data structure that is returned from the fetch. This is what is set to the state "data". It can be called whatever you want. You can iterate through it, and dynamically display the data in JSX if you wanted, but I just hardcoded it for you so it's easier to understand.
const [data, setData] = useState([]);
useEffect(() => {
fetch("https://api.carbonintensity.org.uk/intensity", {
method: "GET",
headers: {
"Content-Type": "application/json"
}
})
.then((res) => res.json())
.then((data) => setData(data))
.catch((e) => console.error(e));
}, []);
console.log("data:", data);
return (
<div>
<p>Track</p>
<div>
<p>From: {data.data["0"].from}</p>
<p>To: {data.data["0"].to}</p>
<div>Intensity:</div>
<p>forecast: {data.data["0"].intensity.forecast}</p>
<p>forecast: {data.data["0"].intensity.actual}</p>
<p>forecast: {data.data["0"].intensity.index}</p>
</div>
</div>
);

How to have conditional defaultValues from useEffect in react-select within react-hook-form?

I'm working on a form using react-hook-form that contains a react-select CreatableSelect multiselect input. The multiselect is used for tags of a given post and it is conditional based on if the user selects to update the tags of an existing post.
My issue is that the defaultValue for the multiselect is not working when a user selects an existing post that contains tags.
The overall flow is: User selects existing post (in PublicShareNetworkSelect in my example) > onChange function changes the post ID stored in hook (selectedNetwork in my example) > change in selectedNetwork fires getNetworkData function that sets the tags variable (networkTags) used as the multiselect defaultValue
Also the getTags() function is used to populate the options in the multiselect.
I believe that the issue as something to do with getting the data from the APIs because I tried to create a minimum reproducible example, but it works exactly how I want it to without the axios calls. However, when I console.log the allTags and networkTags in my full example, there are matching objects in the arrays (the matches should be the defaultValue).
Code example: Main/Parent form component
import React, { useState, useEffect } from "react";
import axios from "axios";
import Form from "react-bootstrap/Form";
import { useForm, Controller } from "react-hook-form";
import CreatableSelect from "react-select/creatable";
import Button from "react-bootstrap/Button";
import PublicShareNetworkSelect from "./publicShareNetworkSelect";
function PublicShareForm(props) {
const {
register,
handleSubmit,
reset,
control,
errors,
watch,
onChange,
} = useForm();
const [loading, setLoading] = useState(false);
const [selectedNetwork, setSelectedNetwork] = useState([]);
const [allTags, setAllTags] = useState();
const [networkTags, setNetworkTags] = useState([]);
//Create axios instance
const axiosSharedNetwork = axios.create();
async function getTags() {
const getAllTagsApi = {
url: "/public-share/get-all-tags",
headers: {
Accept: "application/json",
"Content-Type": "application/json;charset=UTF-8",
},
method: "GET",
};
await axiosSharedNetwork(getAllTagsApi)
.then((response) => {
const resData = response.data;
const tags = resData.map((tag, index) => ({
key: index,
value: tag.tag_id,
label: tag.name,
}));
setAllTags(tags);
setLoading(false);
})
.catch((error) => {
console.log(error.response);
});
}
async function getNetworkData(networkId) {
const getNetworkDataApi = {
url: "/public-share/get-network/" + networkId,
headers: {
Accept: "application/json",
"Content-Type": "application/json;charset=UTF-8",
},
method: "GET",
};
const getNetworkTagsApi = {
url: "/public-share/get-network-tags/" + networkId,
headers: {
Accept: "application/json",
"Content-Type": "application/json;charset=UTF-8",
},
method: "GET",
};
await axiosSharedNetwork(getNetworkDataApi)
.then(async (response) => {
const resData = response.data;
//Set some variables (i.e. title, description)
await axiosSharedNetwork(getNetworkTagsApi)
.then(async (response) => {
const tagResData = response.data;
const tags = tagResData.map((tag, index) => ({
key: index,
value: tag.tag_id,
label: tag.name,
}));
setNetworkTags(tags);
setLoading(false);
})
.catch((error) => {
console.log(error.response);
});
})
.catch((error) => {
console.log(error.response);
});
}
useEffect(() => {
getTags();
getNetworkData(selectedNetwork);
reset({ tags: selectedNetwork });
}, [reset]);
async function onSubmit(data) {
//Handle submit stuff
}
console.log(allTags);
console.log(networkTags);
return (
<Form id="public-share-form" onSubmit={handleSubmit(onSubmit)}>
<Form.Group>
<Form.Label>Create New Version of Existing Shared Network?</Form.Label>
<PublicShareNetworkSelect
control={control}
onChange={onChange}
setSelectedNetwork={setSelectedNetwork}
/>
<Form.Label>Tags</Form.Label>
<Controller
name="tags"
defaultValue={networkTags}
control={control}
render={({ onChange }) => (
<CreatableSelect
isMulti
placeholder={"Select existing or create new..."}
onChange={(e) => onChange(e)}
options={allTags}
defaultValue={networkTags}
classNamePrefix="select"
/>
)}
/>
</Form.Group>
<Button variant="secondary" onClick={props.handleClose}>
Cancel
</Button>
<Button variant="primary" type="submit">
Share
</Button>
</Form>
);
}
export default PublicShareForm;
PublicShareNetworkSelect - the select component that triggers the function to set the existing post id (selectedNetwork):
import React, { useState, useEffect } from "react";
import axios from "axios";
import { Controller } from "react-hook-form";
import Select from "react-select";
function PublicShareNetworkSelect(props) {
const [loading, setLoading] = useState(false);
const [networks, setNetworks] = useState([]);
//Create axios instance
const axiosNetworks = axios.create();
// Add a request interceptor
axiosNetworks.interceptors.request.use(
function (config) {
// Do something before request is sent
setLoading(true);
return config;
},
function (error) {
// Do something with request error
setLoading(false);
return Promise.reject(error);
}
);
// Add a response interceptor
axiosNetworks.interceptors.response.use(
function (response) {
// Any status code that lie within the range of 2xx cause this function to trigger
// Do something with response data
setLoading(true);
return response;
},
function (error) {
// Any status codes that falls outside the range of 2xx cause this function to trigger
// Do something with response error
setLoading(false);
return Promise.reject(error);
}
);
async function getNetworks() {
const getNetworksApi = {
url: "public-share/get-user-networks/" + props.username,
method: "GET",
};
await axiosNetworks(getNetworksApi)
.then(async (response) => {
setNetworks(
response.data.map((network, index) => ({
key: index,
value: network.network_id,
label: network.title,
}))
);
setLoading(false);
})
.catch((error) => {
console.log(error.response);
});
}
useEffect(() => {
getNetworks();
}, []);
function handleChange(data) {
console.log(data);
if (data) {
props.setSelectedNetwork(data.value);
props.getNetworkData(data.value);
} else {
props.setNetworkTitle("");
props.setNetworkDesc("");
}
}
if (!loading) {
if (networks.length === 0) {
return (
<React.Fragment>
<br />
<p className="font-italic text-muted">
You haven't created any public networks yet.
</p>
</React.Fragment>
);
} else {
return (
<Controller
name="tags"
defaultValue={[]}
control={control}
render={(props) => (
<CreatableSelect
isMulti
placeholder={"Select existing or create new..."}
onChange={(e) => onChange(e)}
// defaultValue={networkTags}
options={allTags}
classNamePrefix="select"
{...props}
/>
)}
/>
);
}
} else {
return <React.Fragment>Loading...</React.Fragment>;
}
}
export default PublicShareNetworkSelect;
Edit 1: console.log output for allTags (options) and networkTags (defaultValue)
The problem is, defaultValue is cached at the first render. The same applies to defaultValues property passed to useForm.
Important: defaultValues is cached at the first render within the custom hook. If you want to reset the defaultValues, you should use the reset api.
As quote from the docs suggests - you have to use reset. I've modified your example accordingly. Take a look here. As you can see I'm asynchronously resetting the form and it works.
Also, pay attention to render prop of the Controller - I'm passing down all props given, not only onChange. It's so because there are other important thingies in here (like value). By wrapping your component in Controller you have to provide onChange and value pair at least.
If you want to read more about reset take a look here.

React not re-rendering when setState following API calls

I have been struggling to understand what is going wrong with this simple todo list front end React app, which should interact with an express API.
my React code is:
import React, {Component} from 'react';
class App extends Component {
constructor(){
super();
this.state = {
todos:[],
currentItem: ''
};
this.handleChange = this.handleChange.bind(this);
this.handleADDButton = this.handleADDButton.bind(this);
this.deleteItem = this.deleteItem.bind(this);
this.updateTodo = this.updateTodo.bind(this);
}
componentDidMount(){
fetch('http://ec2-x-xx-xx-xxx.eu-west-2.compute.amazonaws.com:3001/list')
.then(res => res.json())
.then((todosList) =>
{this.setState({'todos': todosList});
});
}
handleChange(event){
this.setState({currentItem: event.target.value});
}
handleADDButton(event){
fetch('http://ec2-x-xx-xx-xxx.eu-west-2.compute.amazonaws.com:3001/post', {
method: 'POST',
headers:{'Content-type': 'application/json'},
body: JSON.stringify({title: this.state.currentItem})
});
}
deleteItem(x){
fetch('http://ec2-x-xx-xx-xxx.eu-west-2.compute.amazonaws.com:3001/' + x, {
method: 'DELETE',
headers:{'Content-type': 'application/json'}
})
}
updateTodo(y){
fetch('http://ec2-x-xx-xx-xxx.eu-west-2.compute.amazonaws.com:3001/' + y, {
method: 'PUT',
headers:{'Content-type': 'application/json'},
body: JSON.stringify({title: this.state.currentItem})
})
}
render() {
return(
<div>
<h1> Todo List </h1>
<ul>
{this.state.todos.map((todo) => <li> {todo.title}
<button type="button" onClick={() => this.deleteItem(todo.key)} >x</button>
<button type="button" onClick={() => this.updateTodo(todo.key)}>update</button> </li>)}
</ul>
<input type="text" value={this.state.currentItem} onChange={this.handleChange} />
<button type="submit" onClick={this.handleADDButton}>ADD</button>
</div>
)
}
}
export default App
The calls do update the API, and if I manually refresh the page, the React app picks up on the new data coming through from the API. However, when clicking the buttons it doesn't re-render by itself.
Say for example I click the ADD Button. It sends an OPTIONS to which I get back a 200 code, a POST which also comes back with a 200 and only sometimes, a GET with a 200. There is no pattern in when it performs the last GET call and also there is no pattern in when it re-renders following a button click. To obtain the latest data I always have to refresh.
Don't know what to make of this and have been stuck for days.
I think there is no state update on button actions
try to add a state updates for the actions same as componentDidMount
For ex:
handleADDButton(event){
event.preventDefault();
fetch('http://ec2-x-xx-xx-xxx.eu-west-2.compute.amazonaws.com:3001/post', {
method: 'POST',
headers:{'Content-type': 'application/json'},
body: JSON.stringify({title: this.state.currentItem})
}).then(res => res.json())
.then((data) => {
this.setState((prevState) {
const todos = [...prevState.todos, data.todo];
return {
todos: todos
}
})
});
}
In this case you have to return the new todo which will catch in data.todo
And for delete action
deleteItem(x){
fetch('http://ec2-x-xx-xx-xxx.eu-west-2.compute.amazonaws.com:3001/' + x, {
method: 'DELETE',
headers:{'Content-type': 'application/json'}
}).then(res => res.json())
.then((data) => {
this.setState((prevState) {
const newTodos = prevState.todos.filter(t => t.key !== x);
return {
todos: newTodos
};
})
});
}
These codes are not tested.
actually you don't have any state update in your code. you have to use "setState" when fetching data from API. I recommended learning arrow function and Hooks and use somethings like AXIOS to manage API calls.

React fetch Firebase get unique key value

does anyone know how to get unique key value from firebase database? i want to store it inside state and print it into console.log.
this is example of unique key
this is the code, i just want to show it in console
import React, { Component } from "react";
export class Test extends Component {
constructor(props) {
super(props);
this.state = {
newId: ""
};
}
componentDidMount() {
fetch("https://redditclone-project.firebaseio.com/data.json", {
method: "get",
headers: {
"Content-Type": "application/json",
Accept: "application/json"
}
})
.then(res => res.json())
.then(res => {
console.log(res);
});
console.log('value key, ex:-LzpvyLJnKgDllnTE-eW');
}
render() {
return <div></div>;
}
}
export default Test;
thanks
edit : inside res
and added my firebase link
If you only want to print the keys from the JSON, you can use Object.keys():
.then(res => res.json())
.then(res => {
console.log(Object.keys(res));
});
Since Object.keys() returns an array, you can then also use for example Array.forEach() to loop over these keys.

componentDidMount not rendering

I have an Header component that suppose to render his child components by a the condition of if the user is logged. It recognize the condition by the session-storage. I tried to control the rendering by
componentDidMount:
renderUserHeader = () => {
if (sessionStorage.getItem('user-auth')) {
var tokenToSend = { token: sessionStorage.getItem('user-auth') }
var regJSONED = JSON.stringify(tokenToSend)
fetch('http://localhost:4000/users/token', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: regJSONED
})
.then(response => {
if (!response.ok) {
throw new Error('HTTP error ' + response.status)
}
return response.text()
})
.then(data => {
let JsonedUserName = JSON.parse(data)
this.setStateUserName(JsonedUserName.name, JsonedUserName.admin)
})
if (!this.state.admin) {
return <UserHeader name={this.state.userName} />
} else if (this.state.admin) {
return <AdminHeader name={this.state.userName} />
}
} else if (!sessionStorage.getItem('user-auth')) {
return (
<Link to='/login'>
{' '}
<LoginLink />{' '}
</Link>
)
}
}
componentDidMount() {
this.renderUserHeader()
}
As you can see the renderUserHeader is being the component did mount but it is not working.
I have tried calling renderUserHeader inside the render and it worked but it keeps bugging and I have to refresh the page everytime.
render() {
return (
<header>
<Logo />
{this.renderUserHeader()}
</header>
)
}
Can someone tell me why componentDidMount doesn't not work?
componentDidMount is used for side effects like fetching data and updating component state only. If you return some component (eg <Link />) from componentDidMount it won't be rendered.
And you should never do any side effects inside render.
Instead, you should fetch and update state in the componentDidMount and based on the state render corresponding components.
componentDidMount not rendering
as said in above answer:componentDidMount is used for side effects like fetching data and updating component state only.
now
how to make it work with your code
in order to make it work,your componentDidMount should be like
componentDidMount(){
if(sessionStorage.getItem('user-auth')){
var tokenToSend = {token: sessionStorage.getItem('user-auth')}
var regJSONED = JSON.stringify(tokenToSend)
fetch('http://localhost:4000/users/token', {
method: 'POST',
headers:{
"Content-Type": "application/json"
},
body: regJSONED,
}).then(response => {
if (!response.ok) {
throw new Error("HTTP error " + response.status);
}
return response.text();
})
.then(data => {
let JsonedUserName = JSON.parse(data)
this.setStateUserName(JsonedUserName.name,JsonedUserName.admin )
})
}
and your renderUserHeader should be like
renderUserHeader = () =>{
if(!sessionStorage.getItem('user-auth')){
return <Link to="/login"> <LoginLink /> </Link>
}
if(!this.state.admin){
return <UserHeader name ={this.state.userName}/>
}
else if(this.state.admin){
return <AdminHeader name ={this.state.userName}/>
}
}
and you can call it from render method.
you can use conditional rendering with ternary operator.
{this.state.isLoggedIn ? (some JSX if true) : (some JSX if false or just NULL)}
in the componentDidMount you can set the isLoggedIn property in the state, so every time the component loads - (render method will run) - the condition will be checked again

Categories

Resources