Use an async function from one component to another - javascript

I'm working on coding a Dapp with the react boxe from truffle. For that application I need to use a function (just two getters) from my smart contract in another component than App.js. This function is the getUser, which needs to be in form from Login.js.
App.js:
class App extends Component {
state = {
web3: null,
contract: undefined,
account: null,
user: {
id: null,
name: '',
password: ''
}
};
componentDidMount = async () => {
try {
const web3 = await getWeb3();
const accounts = await web3.eth.getAccounts();
const networkId = await web3.eth.net.getId();
const deployedNetwork = Login.networks[networkId];
const instance = new web3.eth.Contract(
Login.abi,
deployedNetwork && deployedNetwork.address,
);
this.setState({ web3, accounts, contract: instance });
} catch (error) {
alert(
`Failed to load web3, accounts, or contract.
Check console for details.`,
);
console.error(error);
}
};
getUser = async (event) => {
const userID = this.state.id;
const userName = await this.state.contract.methods
.getUsername(userID).call();
const userPassword = await this.state.contract.methods
.getPassword(userID).call();
console.log(userName + ' - ' +userPassword);
};
render() {
if (!this.state.web3) {
return <div>Loading Web3, accounts, and contract...</div>;
}
return (
<div className="App">
</div>
);
}
}
export default App;
Login.js:
class App extends Component{
handleInputChange = (event) => {
let input = event.target;
let name = event.target.name;
let value = input.value;
this.setState({
[name]: value
})
}
render() {
return (
<div className='Login'>
<form>
<label>
<span>Barcode:</span>
<input name="id" type="number" required
onChange={this.handleInputChange} />
</label>
<button type="submit" value="submit">Get user data</button>
</form>
</div>
);
}
}
export default Login;
I first tried to export the getUser function:
App.js:
export const getUser = async (event) => {
const userID = this.state.id;
const userName = await this.state.contract.methods
.getUsername(userID).call();
const userPassword = await this.state.contract.methods
.getPassword(userID).call();
console.log(userName + ' - ' +userPassword);
};
Login.js:
import {getUser} from ...
But I got the following error:
Unhandled Rejection (TypeError): Cannot read property 'state' of undefined
I then tried to add inside the Login, the same componentDidMount
and state found inside App.js (with the imports), however I got
the following error:
TypeError: Cannot read property 'methods' of null
I no longer have any idea of how to do it. So I would like to ask some help please.
I thank in advance anyone who will take the time to help me.

You just need to pass the value between components. Let's say onClick you will move from login to app component and you will pass id & name.
onClick=(id,name)=>{
history.push({
pathname: '/secondpage', //app component url
state: { id: id, name: name }
})
}
history could be imported from react-router
Then in app component you can access this value in, this.props.location.state
If you want to send value through component, in login component declare app component like this,
<App
id={id}
name={name}
/>
Then in app component you can get the value in this.props

Global State Management
You are looking for a way to manage state at a global level. There are lots of solutions for this problem that exists and are widely used.
Solution 1: Props or "prop drilling"
The idea is to pass the state to every component that needs it. You should only consider doing this if you are passing down props that are not global level or is needed by only a few component trees.
An example of passing down props is in the official docs (https://reactjs.org/docs/context.html)
Solution 2: React Context API
There is already a built in feature inside React that is widely used called Context API that solves this. Example of usage is also in the docs. https://reactjs.org/docs/context.html
Context provides a way to pass data through the component tree without having to
pass props down manually at every level.
TLDR; If you need to your state to be at a global level where lots of component trees need access to it, this is probably the solution.

Related

Update Server Component after data has changed by Client Component in Next.js

I am still trying to wrap my head around this scenario. Can anyone please suggest what is the correct way to do this in Next.js 13?
I diplay a list of users in a Server Component, for example, like this (using MongoDB):
// UsersList.jsx
const UsersList = () => {
const users = await usersCollection.getUsers()
return (
<div>
{users.map(user) => <div>{user}</div>}
</div>
)
}
And on the same page, I have also defined client component for adding users:
// UsersEdit.jsx
'use client'
const UsersEdit = () => {
const handleAdd() => // calls POST to /api/users
return // render input + button
}
Both are displayed together like this in a Server Component Page:
// page.jsx
const Users = () => {
return (
<div>
<UsersList />
<UsersEdit />
</div>
)
}
How should I "reload" or "notify" UsersList that a new user has been added to the collection to force it to display a new user/updated user?
For now, the only way to have the updated data by your Client Component reflected on the Server Component is to call router.refresh(), where router is the returned value by useRouter, after your request to the API. As you can read on the official Next.js doc:
The Next.js team is working on a new RFC for mutating data in Next.js. This RFC has not been published yet. For now, we recommend the following pattern:
You can mutate data inside the app directory with router.refresh().
And they gave a wonderful example, working with a Todo List application. I added it below to have a more complete thread.
Let's consider a list view. Inside your Server Component, you fetch the list of items:
// app/page.tsx
import Todo from "./todo";
async function getTodos() {
const res = await fetch("/api/todos");
const todos = await res.json();
return todos;
}
export default async function Page() {
const todos = await getTodos();
return (
<ul>
{todos.map((todo) => (
<Todo key={todo.id} {...todo} />
))}
</ul>
);
}
Each item has its own Client Component. This allows the component to use event handlers (like onClick or onSubmit) to trigger a mutation.
// app/todo.tsx
"use client";
import { useRouter } from 'next/navigation';
import { useState, useTransition } from 'react';
export default function Todo(todo) {
const router = useRouter();
const [isPending, startTransition] = useTransition();
const [isFetching, setIsFetching] = useState(false);
// Create inline loading UI
const isMutating = isFetching || isPending;
async function handleChange() {
setIsFetching(true);
// Mutate external data source
await fetch(`https://api.example.com/todo/${todo.id}`, {
method: 'PUT',
body: JSON.stringify({ completed: !todo.completed }),
});
setIsFetching(false);
startTransition(() => {
// Refresh the current route and fetch new data from the server without
// losing client-side browser or React state.
router.refresh();
});
}
return (
<li style={{ opacity: !isMutating ? 1 : 0.7 }}>
<input
type="checkbox"
checked={todo.completed}
onChange={handleChange}
disabled={isPending}
/>
{todo.title}
</li>
);
}

React Redux : use Selector is called before dispatch

I'm creating a react app with redux.
I need the lists of french departements for all pages in my app, so I put it in redux state.
I dispatch the action in the App component in the useEffect hook (Note I use an other useEffect in the component, but when the action is in the other block it's not working too)
I have a page where I need to use this list, so I select it with the useSelector hook.
But it returns an empty object, I have an error telling me dpts.map is not a function
I think the action is dispatching after the page has rendered, because I when I log the response of the api call in the action, it appears after the log of the useSelector result.
I'm using another state property in another page, but it seems to work with the other page.
App.jsx
const dispatch = useDispatch();
useEffect(() => {
dispatch(getDpts());
}, [dispatch])
Here is the action associated with :
dpts.actions.js
import axios from "axios";
export const GET_DPTS = "GET_DPTS";
export const getDpts = () => {
return async (dispatch) => {
try {
const res = await axios({
method: "get",
url: "https://geo.api.gouv.fr/departements",
});
console.log("done : " + res)
dispatch({ type: GET_DPTS, payload: res.data });
} catch (err) {
(err) => console.log("DPTS FETCH ERROR --- " + err);
}
};
};
Map.jsx
function DptCtl() {
// Control
const map = useMap();
// List of dpts and provinces
const dpts= useSelector(dptsSelector);
console.log(dpts);
return (
<>
<input type="text" list="dpt-ctl-list" placeholder="Filtrer par département"/>
<datalist id="dpt-ctl-list">
{dpts.map((dpt, index) =>
<option value={dpt.code} key={index}>{dpt.nom}</option>
)}
</datalist>
</>
)
}
It depends on how you are initializing your state in the reducer.
for example you create a reducer with this initial state:
const initialState={}
later, based on actions, the state changes to this:
{dpts:someDataArray}
the problem is that you have a dpts.map somewhere in your app, since dpts is undefined in the beginning you receive that error that dpts.map is not a function.
the solution to this is simply put dpts in the initialState as an empty array:
const initialState={dpts:[]}
if that is not the issue with your code, meaning dpts isn't undefined in the initialState, it is probably initialized as a string or an object which don't have map methods.

How to properly use React.creatRef()

I've been beating my head against the wall and cross referencing code I've read from others and questions that have been asked. I'm at the point where I know I'm just not looking in the right direction, however I'm not sure where to turn.
The app I'm writing was originally written by me in 2019, and I did not know of React h
Hooks as I learned components from a coding bootcamp. TL;DR: I'm rusty.
The issue:
I'm trying to use AXIOS to use a PUT call into my MongoDB, and the way I learned was by using refs. Refs in the way I learned is now deprecated, and I just want to get this working so I can move on to another project and start using Hooks instead.
When I use my button created to save the change, I get an error in the browser console, however it refreshes too fast for me to catch the exact error. The second I get to look at the wall of text, it looks similar to a bad promise, but I'm not entirely sure. Either way, the data does not get updated.
My code:
import React, { Component } from 'react'
import axios from 'axios'
export default class EditSeed extends Component {
constructor(props){
super(props)
this.state = {
_id: '',
seed: '',
created_at: '',
__v: ''
}
this.changeHandler = this.changeHandler.bind(this)
this.myRef = React.createRef
this.focusTextInput = this.focusTextInput.bind(this);
}
focusTextInput() {
// Explicitly focus the text input using the raw DOM API
// Note: we're accessing "current" to get the DOM node
this.textInput.current.focus();
}
componentDidMount(){
this.fetchSeed()
}
fetchSeed = async () => {
try {
const res = await axios.get(`/api/grapevine/${this.props.match.params.id}`)
this.setState({
_id: res.data._id ,
seed: res.data.seed,
created_at: res.data.created_at,
__v: res.data.__v
})
}
catch (err) {
this.setState({ error: err.message })
console.log(err)
}
}
editSeed = async (newSeed) => {
try {
const res = await axios.request({
method: 'put',
url: `/api/grapevine/${this.state._id}`,
data: newSeed
})
res.this.props.history.push(`/`)
}
catch (err) {
this.setState({ error: err.message })
console.log(err)
}
}
onSubmit = (e) => {
const newSeed = {
seed: this.myRef.current.seed.value,
created_at: this.myRef.current.created_at.value
}
this.editSeed(newSeed)
e.preventDefault()
}
changeHandler = (e) => {
const target = e.target
const value = target.value
const name = target.name
this.setState({
[name]: value
})
}
render(){
return(
<div>
<h1>Edit Seed</h1>
<form onSubmit={this.onSubmit.bind(this)}>
<label>
Edit Message:
<input type="text" name="seed" ref={this.myRef} value={this.state.seed} onChange={this.changeHandler} />
</label>
<input type="submit" value="Save" />
</form>
</div>
)
}
}
My fetch function works as intended, it's mainly just the edit that doesn't want to work. I have tried changing
<input type="text" name="seed" ref={this.myRef} value={this.state.seed} onChange={this.changeHandler} />
to
<input type="text" name="seed" ref={this.myRef.current} value={this.state.seed} onChange={this.changeHandler} />
Obviously I am doing something wrong, and I have referenced and read the React docs on this about 10 times and I'm really just hitting a wall.
Any insight would be massively appreciated.
Firstly, React.createRef is a function, so it needs to be invoked.
this.myRef = React.createRef();
Then you simply attach the ref to the element.
<input
ref={this.myRef}
type="text"
name="seed"
value={this.state.seed}
onChange={this.changeHandler}
/>
Then whenever you need to reference the input's ref you access the current value of it.
this.myRef.current
Update
I don't think the React ref is providing much for you here since you duplicate the seed and created_at into local state. You update the seed state via the onChange handler. Why not just forego the ref and access the state in the submit handler. I believe this will resolve your "cannot access value of undefined" error since the input element target object likely doesn't have seed or created_at attributes to access a value of.
onSubmit = (e) => {
e.preventDefault();
const newSeed = {
seed: this.state.seed,
created_at: this.state.created_at // is this supposed to be a current datetime?
}
this.editSeed(newSeed);
}
changeHandler = (e) => {
const { name, value } = e.target;
this.setState({
[name]: value
});
}
You did nearly got it right, just a few minor changes will lead you to the right way:
First: You need to invoke React.createRef function:
this.myRef = React.createRef -> this.myRef = React.createRef()
Second: You need to call e.preventDefault() from the begining of your handler so that the whole page will not get refeshed imediately. Something like this:
onSubmit = (e) => {
e.preventDefault();
const newSeed = {
seed: this.myRef.current.seed.value,
created_at: this.myRef.current.created_at.value
}
this.editSeed(newSeed)
}
From then, I think you will easily get to the right path.
You can use useRef hook in functional components like this:
import React, {useRef} from 'react';
function MyComponenet () {
myRef = useRef(null) // <---- initialize your ref with null
return (
<div ref=myRef >
Hello World
</div>
)
}
now you create a reference and initiate it with a null value, then passed it to the div element. from now you can manipulate your ref like get its value or assign values, click, and other actions.
with TypeScript
myRef = useRef<HTMLDivElement>(null)
if you are passing myRef to a div element, so your ref type will be HTMLDivElement

Using redux to take a time on a stopwatch and put it into a database

I was recommended using redux to place a time into a database but I'm having issues. I have a stopwatch in my index.js that the user can start and stop. After, I have a button that allows them the ability to to put their time into a database. From my node file, I'm getting UnhandledPromiseRejectionWarning: error: null value in column "time" violates not-null constraint. I'm wondering if I'm getting that because I have difference = 0 at the stop of the index file and it doesn't retrieve difference = this.state.endTime - this.state.startTime; or if there is another issue I'm not seeing.
index.js
export let difference = 0;
export default class Home extends React.Component {
constructor(props);
super(props);
this.state = {
startTime: 0,
endTime: 0,
}
}
handleStart = () => {
this.setState({
startTime: Date.now()
})
}
handleStop = () => {
this.setState({
endTime: Date.now()
})
}
render() {
difference = this.state.endTime - this.state.startTime;
return (
<App />
<reducer />
);
}
}
reducer.js
import * as actionTypes from "./actions.js";
import difference from "../pages/index.js";
const initialState = {
count: 0
}
const reducer = (state = initialState, action) => {
switch (action.type) {
case actionTypes.NUMBER:
return { state, count: difference };
default: return state;
}
};
export default reducer;
store.js
import { createStore } from 'redux';
import reducer from './reducer';
const store = createStore(reducer);
export default store;
actions.js
export const NUMBER = 'NUMBER';
App.js
import React from 'react';
import { addName } from "./util";
function App() {
const [name, setName] = React.useState("")
function handleUpdate(evt) {
setName(evt.target.value);
}
async function handleAddName(evt) {
await addName(name);
}
return <div>
<p><input type='text' value={name} onChange={handleUpdate} /></p>
<button className='button-style' onClick={handleAddName}>Add Name</button>
</div>
}
export default App;
util.js
import "isomorphic-fetch"
import difference from "../pages/index.js";
export function addName(name) {
return fetch('http://localhost:3001/addtime', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ name, time: difference })
})
}
node server.js
app.post("/addtime", cors(), async (req,res) => {
const name = req.body.name;
const time = req.body.time;
const timeStamp = dateFormat(time, dateFormat.masks.isoDateTime);
const template = 'INSERT INTO times (name,time) VALUES ($1,$2)';
const response = await pool.query(template, [name,time]);
res.json({name: name, time: time});
});
State Management
In React we manage changing values through state. There is a way to access the value and a way to update the value. When the value changes, React knows to re-render the component.
This state can be stored within a component of your app. When you write const [name, setName] = React.useState(""), name is your "getter" and setName is your "setter".
The state can also be stored inside a Context Provider component that is a parent of the current component. Any children of this Provider can use the useContext hook to access the stored value.
The React Redux package uses these React contexts to make the redux state globally available. You wrap your entire App in a Redux Provider component to enable access.
With Redux, your "getters" are selector functions, which you call though useSelector, and your "setters" are actions that you pass though the dispatch function. React actually supports this syntax of state management on a local component level (without Redux) though the useReducer hook.
You don't need to use contexts in order to pass around state from component to component. You can also pass values and "setters" as props from a parent component to a child. React recommends this approach and has a section on Lifting State Up.
This is just a brief overview of some of the concepts at play. The issues that you are having here are due to state management. There are many possible ways to handle your state here. In my opinion Redux and Contexts are both unnecessary overkill. Of course you can use them if you want to, but you need to set them up properly which you haven't done.
Errors
export let difference = 0;
Variables which exist at the top-level of a file, outside of a component, should be immutable constants only. When you have a value that changes, it needs to be part of your app state.
When you have a "stateful" value like difference you can't just use a variable that exists outside of your components.
render() {
difference = this.state.endTime - this.state.startTime;
...
This is not how you update a value. We don't want to be just setting a constant. We also don't want to trigger changes of a stateful value on every render as this creates infinite re-render situations.
handleStart & handleStop
The functions themselves are fine but they are never called anywhere. Therefore difference will always be 0.
<reducer />
A reducer is not a component so you cannot call it like this. It is a pure function. In order to introduce redux into your JSX code, you use the reducer to create a store variable and you pass that store to a react-redux Provider component.
const reducer = (state = initialState, action) => {
switch (action.type) {
case actionTypes.NUMBER:
return { state, count: difference };
default: return state;
}
};
You want to include the difference as a property of your action rather than accessing an external variable. The standard convention is to add a payload property to the action. The payload could be the difference number itself or an object with a property difference. That sort of design choice is up to you. An action might look like { type: actionTypes.NUMBER, payload: 0.065 } or { type: actionTypes.NUMBER, payload: { difference: 0.065 } }.
Your state is an object with a property count. Your reducer should return the next state, which should also be an object with a property count. This will not return the right state shape: return { state, count: difference };.
It is typical to use the spread operator ... to copy all other properties of the state and update just one (or a few), like this: return { ...state, count: difference }. However your state does not have any other properties, so that is the same as return { count: difference };. Since you are just storing a single number, there is no value from the previous state that is copied or preserved. (Which is a large part of why I think that Redux is not helpful or necessary here.)
There may be some issues on the backend as well, but there are such serious issues with the front end that I think that's your main problem.
Structuring Components
Think about what your app needs to do. What actions does it respond to? What information does it need to know?
Based on your description, it needs to:
Start a timer when a button is clicked
Stop a timer when a button is clicked
Store the most recent timer value
Allow a user to enter and update a name
Store that name
POST to the backend when a button is clicked, if there is a stored difference and name
Next you break that up into components. Store each stateful value at the highest component in the tree which needs to access it, and pass down values and callbacks as props. The startTime and endTime are only used by the timer itself, but the timer need to set the difference after stopping. The difference would be stored higher up because it is also needed for the POST request.
Think about what buttons and actions are available at any given time, and what conditions are used to determine this. For example, you shouldn't be able to stop a timer before you have started it. So we would see if startTime is greater than 0.
Minimal Front-End Example
import * as React from "react";
const Timer = ({ setDifference }) => {
const [startTime, setStartTime] = React.useState(0);
const handleStart = () => {
setStartTime(Date.now());
};
const handleStop = () => {
const difference = Date.now() - startTime;
setDifference(difference);
};
return (
<div>
<button onClick={handleStart}>
Start
</button>
<button disabled={startTime === 0} onClick={handleStop}>
Stop
</button>
</div>
);
};
const Submission = ({ time }) => {
const [name, setName] = React.useState("");
function handleUpdate(evt) {
setName(evt.target.value);
}
async function handleAddName(evt) {
return await fetch("http://localhost:3001/addtime", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ name, time })
});
}
return (
<div>
<div>Time: {time}</div>
<input type="text" value={name} onChange={handleUpdate} />
<button onClick={handleAddName} disabled={name.length === 0}>
Add Name
</button>
</div>
);
};
const App = () => {
const [time, setTime] = React.useState(0);
return (
<div>
<Timer setDifference={setTime} />
{time > 0 && <Submission time={time} />}
</div>
);
};
export default App;
CodeSandbox Link
You might not be saving any value to DOB column, you need to allow NULL value. You should change to
DOB = models.DateField(auto_now = False, null = True)
You should also make sure DOB column in your table allows null.

How would I pass a getParam param to another screen using react-navigation in a stateless component to fire up a graphql mutation?

Following the docs: react-navigation params, I'd basically do something like this (parent):
this.props.navigation.navigate('Details', {
itemId: 86,
otherParam: 'anything you want here',
});
And then passing to the child
class DetailsScreen extends React.Component {
render() {
const { navigation } = this.props;
const itemId = navigation.getParam('itemId', 'NO-ID');
const otherParam = navigation.getParam('otherParam', 'some default value');
return ( (..omited for brevity)
EDIT:
My situation is that in one screen/container we fire up a graphql mutation that takes a phoneNumber and retrieves a code to user.
Onto the next screen I need to take this phoneNumber that the user has just inserted to fire another mutation that takes phoneNumber and code to make the authentication. I don't want to use states to do that, since there's api available on react-navigation.
Parent:
signInUser(phoneNumber: string) {
const { navigation, sendCode } = this.props
sendCode({ variables: { phoneNumber } }) // graphql mutation
navigation.navigate('Authenticate', { phoneNumber }) // passing
// phoneNumber as param
}
Child:
export const AuthenticationCodeModal = (props: NavigationScreenProps) => {
const {navigation} = props
const phoneNumber = navigation.getParam(phoneNumber)
// console.log(phoneNumber)
// I need to get phoneNumber here and pass it as props to
// <AuthenticationFormContainer> which will fire up auth mutation
return(
<View style={styles.container} >
<AuthenticationFormContainer/>
</View>
)
}
It better and interesting if you use redux for task like this. Redux has a global state which can be assessed by any component
Turns out it was a simple syntax error of the value that getParam receives, doing this fixed my problem:
navigation.navigate('Authenticate', { phoneNumber: {phoneNumber: phoneNumber} })

Categories

Resources