Newbie question.
I'm learning ReactJS in Typescript and am coming from an AngularJS background (trying to unlearn AngularJS).
The code below runs and displays the props.compiler. When I tap the H1, the handler fires but the React code doesn't update the display like it would in AngularJS.
What am I missing?
export interface IHelloProps {
compiler: string;
framework: string;
}
export class Hello extends React.Component<IHelloProps> {
constructor(props: IHelloProps) {
super(props);
this.x = props.compiler;
}
private x: string;
get Name(): string {
return this.x;
}
set Name(value: string) {
this.x = value;
}
render() {
return <h1 onClick={this.handler}>Hello from {this.Name}!</h1>;
}
handler = async (t: any) => {
const resp = await fetch('https://api.github.com');
const data = await resp.json();
this.Name = data.current_user_url;
};
}
There is no two-way binding like in AngularJS, you need to use setState in order to schedule an update to a component’s state object:
// A bit more React-Class oriented example
class App extends React.Component {
state = {
currentUser: this.props.compiler
};
handler = async t => {
const resp = await fetch('https://api.github.com');
const data = await resp.json();
this.setState({ currentUser: data.current_user_url });
};
render = () => {
const { currentUser } = this.state;
return <h1 onClick={this.handler}>Hello from {currentUser}!</h1>;
};
}
// Original OP class
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
x: props.compiler
};
}
get Name() {
return this.state.x;
}
set Name(value) {
this.setState({ x: value });
}
handler = async t => {
const resp = await fetch('https://api.github.com');
const data = await resp.json();
this.setState({ x: data.current_user_url });
};
render = () => {
return <h1 onClick={this.handler}>Hello from {this.Name}!</h1>;
};
}
Try reading the Getting Started section which explains it in depth.
In React you don't set the values like you did in your setter function.
React renders the DOM asynchronously by comparing the virtual DOM.
So you need to use setState() to update the state of your DOM.
You can add state in your code and pass it to the React component.
handler = async (t: any)=> {
const resp = await fetch('https://api.github.com');
const data = await resp.json();
this.Name=data.current_user_url;
this.setState({name:data.current_user_url}); //name being the state variable
}
Hope it helps!!
TypeScript's interface is purely for typechecking. You should not be directly referencing any of its properties.
Instead, I would recommend you to create an interface for your state, which holds the values of current_user_url from your fetch request.
Then, on your click handler, you update the state by calling setState() to re-render the component with the updated state. On important thing to take note, is that you should never mutate state directly.
In order to fully implement TypeScript's typechecking capabilities on your component, you should use provide React.Component with its prop and state type parameters.
I have modified your code such that your component is TypeScript-compliant. Do take note of the new IHelloState interface.
export interface IHelloProps {
compiler: string;
framework: string;
}
export interface IHelloState {
name: string;
}
export class Hello extends React.Component<IHelloProps, IHelloState> {
constructor(props: IHelloProps) {
super(props);
this.state = {
name: '',
};
}
handler = async (t: React.MouseEvent<HTMLButtonElement>)=> {
const resp = await fetch('https://api.github.com');
const data = await resp.json();
this.setState({
name: data.current_user_url,
});
}
render() {
const { name } = this.state;
return <h1 onClick={this.handler}>
Hello from {name}
</h1>;
}
}
You may read more about working with React and TypeScript over here.
Related
I have a two Api's which gets some source and another to add sources.
While displaying the source(images ..) on the screen, in the mean time I am adding some new sources. Since componentDidMount runs only at the start, I can not force componentDidMount to run again whenever a new source added
Here the part of the code:
App.tsx:
export class App extends React.Component<MyProps, MyState> {
constructor(props: any) {
super(props)
this.state = {
sources: [],
currentImage: -1,
}
}
async getSources() {
let allData = await axios.get('/playlist')
return allData.data.data // getting allData from the Api
}
async componentDidMount() {
const sources = await this.getSources()
this.setState(
{
sources: sources,
currentImage: 0,
}
)
}
render() {
<div>
<Playlist onChange={() => this.getSources()} />
.
.
.
</div>
}
}
Playlist.tsx
export function Playlist(props: {onChange: () => void}) {
const {register, handleSubmit} = useForm()
const [data, setData] = useState('')
const onSubmit = async (data: any) => {
console.log('data', await props.onChange())
const url = '/add'
try {
await axios.post(url, data)
await props.onChange(). // trying to call the function after adding new source
} catch (error) {
console.log('Error')
}
}
return (
<form onSubmit={handleSubmit(onSubmit)}>
...
</form>
)
}
As seen in the code, I try to call the function inside the componentDidMount again(after new source is added) to update sources but seems it doesn't work.
I am trying to use the search api of Open Library.
Usually, if you are going to use a functional component, you will do it like this on your api file:
import axios from 'axios';
export default axios.create({
baseuRL: 'http://openlibrary.org/search.json'
})
And then you will import this on a file where you will fetch the data:
import booksAPI from '../apis/books';
const books = () => {
useEffect(() => {
books()
}, [])
const books = async() => {
const res = await booksAPI.get('?author=tolkien');
console.log(res.data);
}
}
This is expected to console.log the data on your terminal. However, using the class component with axios and componentDidMount to fetch the data.
class BookList extends Component {
constructor(props) {
super(props)
this.state = {
books: []
}
}
componentDidMount(){
const booksResponse = async() => {
const response = await booksAPI.get('?author=tolkien');
console.log(response.data)
}
}
This is complaining about the await keyword and doesn't console.log the data. Also, I am not sure how I can convert the useEffect to a class component so it can perform side effects?
const BookList = () => {
// State variable where you can store the data
const [books, setBooks] = useState([]);
// Effect, which would be called on component mount because of empty array of dependencies -> [] (look for useEffect lifecycles) and set the data to state variable. After this, component will re-render.
useEffect(() => {
const fetchBooks = async () => {
const response = await booksAPI.get('?author=tolkien');
setBooks(response.data);
console.log(response.data);
}
fetchBooks();
}, []);
return ...
}
Also, make sure that you're adding query/mutation/subscription to your GraphQL document right before the name of query/mutation/subscription, as someones told you in the comment.
In case you're looking for class component realization, your code should look like this:
class BookList extends Component {
constructor(props) {
super(props)
this.state = {
books: []
}
}
fetchBooks = () => {
const response = booksAPI.get('?author=tolkien');
this.setState({ books: response.data });
}
componentDidMount(){
fetchBooks();
}
...
}
Here I have created a simple example in React for you.
componentDidMount() {
this.booksResponse();
}
booksResponse = async () => {
const response = await axios.get(
'https://openlibrary.org/search.json?author=token'
);
console.log(response.data);
};
Here is Stackblitz link.
https://stackblitz.com/edit/react-zk8nhq
If you find any confusing then Please comment here I can create more example for you.
I have a variable that I want to keep track of and update its value between two classes. In one of my classes, I started using props like this with the variable isLoading in my Post class:
class Post extends React.Component {
constructor(props) {
super(props);
this.state = {
isLoading: false
};
}
post = () => {
this.props.uploadPost()
this.props.updatePhoto()
this.props.updateDescription('')
this.props.navigation.navigate('Home')
}
openLibrary = async () => {
const { status } = await Permissions.askAsync(Permissions.CAMERA_ROLL)
if (status === 'granted') {
const image = await ImagePicker.launchImageLibraryAsync()
if(!image.cancelled ){
this.setState({ isLoading: true });
const resize = await ImageManipulator.manipulateAsync(image.uri, [], { format: 'jpeg', compress: 0.1 })
const url = await this.props.uploadPhoto(resize.uri)
this.props.updatePhoto(url)
this.setState({ isLoading: false });
}
}
}
...
Now, I also have another class called Camera that I want to update this same variable. However, I'm not implementing a child like function where I call Post or Camera class in each other.
This is my code for Camera.
class CameraUpload extends React.Component {
state = {
type: Camera.Constants.Type.back,
};
snapPhoto = async () => {
const { status } = await Camera.requestPermissionsAsync();
if (status === 'granted') {
const image = await this.camera.takePictureAsync()
global.config.loading = true;
image ? this.props.navigation.navigate('Post') : null
if( !image.cancelled ){
const resize = await ImageManipulator.manipulateAsync(image.uri, [], { format: 'jpeg', compress: 0.1 })
const url = await this.props.uploadPhoto(resize.uri)
this.props.updatePhoto(url)
loading = false;
// url ? this.props.navigation.navigate('Post') : null
}
}
}
I tried using a global config variable but the variable's value was not getting updated between classes. Please let me know what the best way to go about solving this problem is. Thanks!
React Context
You can use the concept of "Context" in react. You may read about it here
https://reactjs.org/docs/context.html
Async Storage
Also you can use async storage if it suits your design
You can make one utility class:
import AsyncStorage from '#react-native-community/async-storage';
export async function storeData(key, value) {
try {
await AsyncStorage.setItem(key, value)
} catch (e) {
console.log("Error Storing in AsyncStorage stroing " + key + " in async Storage")
}
}
export async function getData(key) {
try {
const value = await AsyncStorage.getItem(key)
return (value == null || value == undefined) ? undefined : value
} catch (e) {
console.log("Error Reading in AsyncStorage stroing " + key + " in async Storage")
}
}
You can store and get data through key-value pairs.
// Firstly import it in your js
import * as asyncStorage from './AsyncStorage'
//For storingthe data:
await asyncStorage.storeData("key1", "value1");
// For getting the data:
await asyncStorage.getData("key1")
You can try global variable:
class post {
onpost(){
global.isLoading = true;
}
}
class cameraupload {
componentDidMount(){
global.isLoading = false;
}
}
Context is the way to go for small pieces of shared state, in my opinion.
Combined with hooks, it's very easy to access state and call functions from any child component.
Define your provider with any shared state and functions
import React, { createContext, useState } from 'react'
const Context = createContext()
const MySharedStateProvider = ({ children }) => {
const [myState, setMyState] = useState('hello')
const updateMyState = value => setMyState(value)
return (
<Context.Provider value={{ myState, updateMyState }}>
{children}
</Context.Provider>
)
}
export { MySharedStateProvider, Context as MySharedStateContext }
Create a hook for your Context, that can be used in any child component
import { useContext } from 'react'
import { MySharedStateContext } from './MySharedStateProvider.js'
export const useMySharedState = () => useContext(MySharedStateContext)
Wrap your components with the provider (I know it wouldn't look like this, it's just an example)
<MySharedStateProvider>
<Posts/>
<CameraUpload/>
</MySharedStateProvider>
Now in your Post or CameraUpload component, you use your hook to get the values
import { useMySharedState } from './MySharedStateHook.js'
const Post = () => {
const { myState, setMyState } = useMySharedState()
}
The way I cache data in Class component is like below :
1. make async API call in componentDidMount
2. get API response and dispatch data through redux
3. use response data by mapping state to prop
What I want to do is caching data right after you get API response with mapped redux state value
inside of useEffect in function component
(
It works on class component. but I'm wondering how should I make it work in function component)
export class MyClassComponent extends React.Component {
private readonly _myCachedData = {
'data1': {}
}
public async componentDidMount() {
await this.loadAsyncData();
}
private loadAsyncData() {
const { makeAPICall } = this.props
await makeAPICall();
return this._myCachedData.data1 = this.props.data1FromReduxConnect;
}
}
export const mapStateTopProps = (state) => {
const { data1FromReduxConnect } = state;
return data1FromReduxConnect;
}
...
What I have tried :
export const MyFunctionComponent = props => {
const { data1FromReduxConnect } = props;
const myCachedData = React.useRef();
const loadAsyncData = () => {
const { makeAPICall } = this.props
await makeAPICall();
return myCachedData.current = data1FromReduxConnect;
}
React.useEffect(()=> {
await loadAsyncData();
})
}
export const mapStateTopProps = (state) => {
const { data1FromReduxConnect } = state;
return data1FromReduxConnect;
}
I was only able to get the previous value ofdata1FromReduxConnect unlike class component did get updated value this.props.data1FromReduxConnect after API call
Not sure if I should just keep class component for it, or is there a way to deal with this issue!
Thanks!!
I don't think that is the right way to use the useRef hook. Similar to React's class components' createRef(), it is actually used to access the DOM in functional components.
If the HTTP request happens only once when MyFunctionComponent is initialised, we can use [] as the second argument in the useEffect hook which will cause this effect to be run only once. In addition, we will need to make use of useState hook to keep track of the component's state which is to be updated with the values from the redux store.
export const MyFunctionComponent = props => {
const { data1FromReduxConnect } = props;
const [ myData, setMyData ] = useState();
const loadAsyncData = async() => {
const { makeAPICall } = this.props
await makeAPICall();
}
useEffect(()=> {
async function getData() {
await loadAsyncData();
}
getData();
// do the rest to get and store data from redux
setMyData(data1FromReduxConnect);
}, [])
}
I have a container component in which I get the ID and drop this ID into the function and the request goes, in principle, the props should come right away, but they are undefined. But when you re-enter the same component, the necessary props are shown.
Explain how to make props appear on the first render?
class View extends React.Component {
componentDidMount() {
let id = this.props.match.params.id;
this.props.GetProjData(id);
}
render() {
return <ProjView {...this.props}></ProjView>;
}
}
let mapStateToProps = state => {
return {
initialValues: {
NameProj: state.project.OneProject.NameProj,
Text: state.project.OneProject.Text,
target: state.project.OneProject.target,
startdate: state.project.OneProject.startdate,
enddate: state.project.OneProject.enddate
},
error: state.settings.error,
loading: state.settings.loading
};
};
My request
export const GetProjData = data => async (
dispatch,
getState,
{ getFirestore }
) => {
const firestore=getFirestore()
try {
await firestore
.collection("Projects")
.where("idProject", "==", data)
.get().then(snap => {
snap.forEach(doc => {
let project=doc.data()
console.log(doc.data());
dispatch({type:getOne,project})
});
})
} catch (err) {}
};
If I'm understanding the flow of your app correctly, you need to account for the renders between when you request your project data and when you receive the project data.
class View extends React.Component {
// constructor fires first so we might as well move it here
constructor(props) {
const id = props.match.params.id;
props.GetProjData(id);
}
render() {
// Your component will rerender before receiving the new data.
// We block the component from mounting so that initialValues
// gets set only when we have the data needed
if (this.props.initialValues && this.props.initialValues.NameProj) {
// A better way to do this would be to listen to a loading variable
// that gets updated when your request finishes
return <ProjView {...this.props} />;
}
return null; // or loading graphic
}
}