Web3.utils.hexToUtf8 getting "undefined" error - reactjs

I am trying to return an username from a struct inside a mapping and I am getting this error:
Error: The parameter "undefined" must be a valid HEX string.
contract UserStorage is BaseStorage {
event UserCreated(string _message);
struct Profile {
uint userId;
bytes32 username;
}
mapping(address => Profile) public profiles;
mapping(address => bool) public addresses;
mapping (bytes32 => uint) public usernames;
uint latestUserId = 0;
function createUser(
address _address,
bytes32 _username
) public onlyControllerAddr returns(uint) {
latestUserId++;
profiles[_address] = Profile(
latestUserId,
_username
);
addresses[_address] = true;
usernames[_username] = latestUserId;
emit UserCreated("Membership Confirmed");
return latestUserId;
}
}
class UserAccount extends React.Component {
state = { dataKey: null };
componentDidMount() {
const { drizzle, drizzleState } = this.props;
const contract = drizzle.contracts.UserStorage;
const account = drizzleState.accounts[0];
const dataKey = contract.methods['profiles'].cacheCall(account);
this.setState({ dataKey });
}
render() {
const { UserStorage } = this.props.drizzleState.contracts;
const displayData = UserStorage.profiles[this.state.dataKey];
const user = displayData && displayData.value[1];
const username = Web3.utils.hexToUtf8(user);
console.log(user);
return (
<div>Hello: {user}</div>
)
}
}
export default UserAccount;
Here are values that are returned:
user:
0x426f620000000000000000000000000000000000000000000000000000000000
const displayData:
{args: Arguments(1), fnIndex: 6, value: Result, error: null}args: Arguments ["0x1C16e11e45Ae1601186E05EE64f38276FAb2239a", callee: (...), Symbol(Symbol.iterator): ƒ]error: nullfnIndex: 6value: Result {0: "1", 1: "0x426f620000000000000000000000000000000000000000000000000000000000", userId: "1", username: "0x426f620000000000000000000000000000000000000000000000000000000000"}__proto__: Object

Related

Keep getting call revert exception error whenever I try to run Crowd-Funding Dapp

So I have been following this tutorial to build a Crowd-Funding Dapp using blockchain, solidity, react. And I keep getting the following mentioned error whenever I start React App. I think it might be related to contract address. The above image is what I get after I click on inspect. As I am still a beginner in Blockchain and programming in general it would be really helpful if anyone can suggest any solutions.
The error
Blockchain loaded
blockchain.jsx:219 call revert exception [ See: https://links.ethers.org/v5-errors-CALL_EXCEPTION ] (method="getProjects()", data="0x", errorArgs=null, errorName=null, errorSignature=null, reason=null, code=CALL_EXCEPTION, version=abi/5.7.0)
blockchain.jsx:220 Uncaught (in promise) Error: No ethereum object.
at reportError (blockchain.jsx:220:1)
at loadProjects (blockchain.jsx:116:1)
at async Home.jsx:13:1
Here is the contract file which is Genesis.sol and the blockchain.jsx in which the error occurs.
Genesis.sol
//SPDX-License-Identifier: MIT
pragma solidity ^0.8.7;
contract Genesis {
address public owner;
uint public projectTax;
uint public projectCount;
uint public balance;
statsStruct public stats;
projectStruct[] projects;
mapping(address => projectStruct[]) projectsOf;
mapping(uint => backerStruct[]) backersOf;
mapping(uint => bool) public projectExist;
enum statusEnum {
OPEN,
APPROVED,
REVERTED,
DELETED,
PAIDOUT
}
struct statsStruct {
uint totalProjects;
uint totalBacking;
uint totalDonations;
}
struct backerStruct {
address owner;
uint contribution;
uint timestamp;
bool refunded;
}
struct projectStruct {
uint id;
address owner;
string title;
string description;
string imageURL;
uint cost;
uint raised;
uint timestamp;
uint expiresAt;
uint backers;
statusEnum status;
}
modifier ownerOnly(){
require(msg.sender == owner, "Owner reserved only");
_;
}
event Action (
uint256 id,
string actionType,
address indexed executor,
uint256 timestamp
);
constructor(uint _projectTax) {
owner = msg.sender;
projectTax = _projectTax;
}
function createProject(
string memory title,
string memory description,
string memory imageURL,
uint cost,
uint expiresAt
) public returns (bool) {
require(bytes(title).length > 0, "Title cannot be empty");
require(bytes(description).length > 0, "Description cannot be empty");
require(bytes(imageURL).length > 0, "ImageURL cannot be empty");
require(cost > 0 ether, "Cost cannot be zero");
projectStruct memory project;
project.id = projectCount;
project.owner = msg.sender;
project.title = title;
project.description = description;
project.imageURL = imageURL;
project.cost = cost;
project.timestamp = block.timestamp;
project.expiresAt = expiresAt;
projects.push(project);
projectExist[projectCount] = true;
projectsOf[msg.sender].push(project);
stats.totalProjects += 1;
emit Action (
projectCount++,
"PROJECT CREATED",
msg.sender,
block.timestamp
);
return true;
}
function updateProject(
uint id,
string memory title,
string memory description,
string memory imageURL,
uint expiresAt
) public returns (bool) {
require(msg.sender == projects[id].owner, "Unauthorized Entity");
require(bytes(title).length > 0, "Title cannot be empty");
require(bytes(description).length > 0, "Description cannot be empty");
require(bytes(imageURL).length > 0, "ImageURL cannot be empty");
projects[id].title = title;
projects[id].description = description;
projects[id].imageURL = imageURL;
projects[id].expiresAt = expiresAt;
emit Action (
id,
"PROJECT UPDATED",
msg.sender,
block.timestamp
);
return true;
}
function deleteProject(uint id) public returns (bool) {
require(projects[id].status == statusEnum.OPEN, "Project no longer opened");
require(msg.sender == projects[id].owner, "Unauthorized Entity");
projects[id].status = statusEnum.DELETED;
performRefund(id);
emit Action (
id,
"PROJECT DELETED",
msg.sender,
block.timestamp
);
return true;
}
function performRefund(uint id) internal {
for(uint i = 0; i < backersOf[id].length; i++) {
address _owner = backersOf[id][i].owner;
uint _contribution = backersOf[id][i].contribution;
backersOf[id][i].refunded = true;
backersOf[id][i].timestamp = block.timestamp;
payTo(_owner, _contribution);
stats.totalBacking -= 1;
stats.totalDonations -= _contribution;
}
}
function backProject(uint id) public payable returns (bool) {
require(msg.value > 0 ether, "Ether must be greater than zero");
require(projectExist[id], "Project not found");
require(projects[id].status == statusEnum.OPEN, "Project no longer opened");
stats.totalBacking += 1;
stats.totalDonations += msg.value;
projects[id].raised += msg.value;
projects[id].backers += 1;
backersOf[id].push(
backerStruct(
msg.sender,
msg.value,
block.timestamp,
false
)
);
emit Action (
id,
"PROJECT BACKED",
msg.sender,
block.timestamp
);
if(projects[id].raised >= projects[id].cost) {
projects[id].status = statusEnum.APPROVED;
balance += projects[id].raised;
performPayout(id);
return true;
}
if(block.timestamp >= projects[id].expiresAt) {
projects[id].status = statusEnum.REVERTED;
performRefund(id);
return true;
}
return true;
}
function performPayout(uint id) internal {
uint raised = projects[id].raised;
uint tax = (raised * projectTax) / 100;
projects[id].status = statusEnum.PAIDOUT;
payTo(projects[id].owner, (raised - tax));
payTo(owner, tax);
balance -= projects[id].raised;
emit Action (
id,
"PROJECT PAID OUT",
msg.sender,
block.timestamp
);
}
function requestRefund(uint id) public returns (bool) {
require(
projects[id].status != statusEnum.REVERTED ||
projects[id].status != statusEnum.DELETED,
"Project not marked as revert or delete"
);
projects[id].status = statusEnum.REVERTED;
performRefund(id);
return true;
}
function payOutProject(uint id) public returns (bool) {
require(projects[id].status == statusEnum.APPROVED, "Project not APPROVED");
require(
msg.sender == projects[id].owner ||
msg.sender == owner,
"Unauthorized Entity"
);
performPayout(id);
return true;
}
function changeTax(uint _taxPct) public ownerOnly {
projectTax = _taxPct;
}
function getProject(uint id) public view returns (projectStruct memory) {
require(projectExist[id], "Project not found");
return projects[id];
}
function getProjects() public view returns (projectStruct[] memory) {
return projects;
}
function getBackers(uint id) public view returns (backerStruct[] memory) {
return backersOf[id];
}
function payTo(address to, uint256 amount) internal {
(bool success, ) = payable(to).call{value: amount}("");
require(success);
}
}
blockchain.jsx
import abi from '../abis/src/contracts/Genesis.sol/Genesis.json'
import address from '../abis/contractAddress.json'
import { getGlobalState, setGlobalState } from '../store'
import { ethers } from 'ethers'
const { ethereum } = window
const contractAddress = address.address
const contractAbi = abi.abi
const connectWallet = async () => {
try {
if (!ethereum) return alert('Please install Metamask')
const accounts = await ethereum.request({ method: 'eth_requestAccounts' })
setGlobalState('connectedAccount', accounts[0]?.toLowerCase())
} catch (error) {
reportError(error)
}
}
const isWallectConnected = async () => {
try {
if (!ethereum) return alert('Please install Metamask')
const accounts = await ethereum.request({ method: 'eth_accounts' })
setGlobalState('connectedAccount', accounts[0]?.toLowerCase())
window.ethereum.on('chainChanged', (chainId) => {
window.location.reload()
})
window.ethereum.on('accountsChanged', async () => {
setGlobalState('connectedAccount', accounts[0]?.toLowerCase())
await isWallectConnected()
})
if (accounts.length) {
setGlobalState('connectedAccount', accounts[0]?.toLowerCase())
} else {
alert('Please connect wallet.')
console.log('No accounts found.')
}
} catch (error) {
reportError(error)
}
}
const getEtheriumContract = async () => {
const connectedAccount = getGlobalState('connectedAccount')
if (connectedAccount) {
const provider = new ethers.providers.Web3Provider(ethereum)
const signer = provider.getSigner()
const contract = new ethers.Contract(contractAddress, contractAbi, signer)
return contract
} else {
return getGlobalState('contract')
}
}
const createProject = async ({
title,
description,
imageURL,
cost,
expiresAt,
}) => {
try {
if (!ethereum) return alert('Please install Metamask')
const contract = await getEtheriumContract()
cost = ethers.utils.parseEther(cost)
await contract.createProject(title, description, imageURL, cost, expiresAt)
} catch (error) {
reportError(error)
}
}
const updateProject = async ({
id,
title,
description,
imageURL,
expiresAt,
}) => {
try {
if (!ethereum) return alert('Please install Metamask')
const contract = await getEtheriumContract()
await contract.updateProject(id, title, description, imageURL, expiresAt)
} catch (error) {
reportError(error)
}
}
const deleteProject = async (id) => {
try {
if (!ethereum) return alert('Please install Metamask')
const contract = await getEtheriumContract()
await contract.deleteProject(id)
} catch (error) {
reportError(error)
}
}
const loadProjects = async () => {
try {
if (!ethereum) return alert('Please install Metamask')
const contract = await getEtheriumContract()
const projects = await contract.getProjects()
const stats = await contract.stats()
setGlobalState('stats', structureStats(stats))
setGlobalState('projects', structuredProjects(projects))
} catch (error) {
reportError(error)
}
}
const loadProject = async (id) => {
try {
if (!ethereum) return alert('Please install Metamask')
const contract = await getEtheriumContract()
const project = await contract.getProject(id)
setGlobalState('project', structuredProjects([project])[0])
} catch (error) {
alert(JSON.stringify(error.message))
reportError(error)
}
}
const backProject = async (id, amount) => {
try {
if (!ethereum) return alert('Please install Metamask')
const connectedAccount = getGlobalState('connectedAccount')
const contract = await getEtheriumContract()
amount = ethers.utils.parseEther(amount)
await contract.backProject(id, {
from: connectedAccount,
value: amount._hex,
})
} catch (error) {
reportError(error)
}
}
const getBackers = async (id) => {
try {
if (!ethereum) return alert('Please install Metamask')
const contract = await getEtheriumContract()
let backers = await contract.getBackers(id)
setGlobalState('backers', structuredBackers(backers))
} catch (error) {
reportError(error)
}
}
const payoutProject = async (id) => {
try {
if (!ethereum) return alert('Please install Metamask')
const connectedAccount = getGlobalState('connectedAccount')
const contract = await getEtheriumContract()
await contract.payOutProject(id, {
from: connectedAccount,
})
} catch (error) {
reportError(error)
}
}
const structuredBackers = (backers) =>
backers
.map((backer) => ({
owner: backer.owner.toLowerCase(),
refunded: backer.refunded,
timestamp: new Date(backer.timestamp.toNumber() * 1000).toJSON(),
contribution: parseInt(backer.contribution._hex) / 10 ** 18,
}))
.reverse()
const structuredProjects = (projects) =>
projects
.map((project) => ({
id: project.id.toNumber(),
owner: project.owner.toLowerCase(),
title: project.title,
description: project.description,
timestamp: new Date(project.timestamp.toNumber()).getTime(),
expiresAt: new Date(project.expiresAt.toNumber()).getTime(),
date: toDate(project.expiresAt.toNumber() * 1000),
imageURL: project.imageURL,
raised: parseInt(project.raised._hex) / 10 ** 18,
cost: parseInt(project.cost._hex) / 10 ** 18,
backers: project.backers.toNumber(),
status: project.status,
}))
.reverse()
const toDate = (timestamp) => {
const date = new Date(timestamp)
const dd = date.getDate() > 9 ? date.getDate() : `0${date.getDate()}`
const mm =
date.getMonth() + 1 > 9 ? date.getMonth() + 1 : `0${date.getMonth() + 1}`
const yyyy = date.getFullYear()
return `${yyyy}-${mm}-${dd}`
}
const structureStats = (stats) => ({
totalProjects: stats.totalProjects.toNumber(),
totalBacking: stats.totalBacking.toNumber(),
totalDonations: parseInt(stats.totalDonations._hex) / 10 ** 18,
})
const reportError = (error) => {
console.log(error.message)
throw new Error('No ethereum object.')
}
export {
connectWallet,
isWallectConnected,
createProject,
updateProject,
deleteProject,
loadProjects,
loadProject,
backProject,
getBackers,
payoutProject,
}
I tried running these commands in separate terminals
npx hardhat node
npx hardhat run scripts/deploy.js --network localhost
The contract gets deployed but the error continues and I should be able to create new projects for Crowdfunding Dapp but because of this error the projects do not get loaded.

Why can't I save the 'id' on MongoDB using Deno backend?

I am trying to save the id that I get from the MongoDB while saving the stock information as a property of stock using the following code but I get some errors:
async save(stock : Stock) {
const id = await stockCollection.insertOne(stock);
console.log('this is the id: ', id.toString());
stock.id = id.toString();
console.log(stock.id);
// delete stock._id;
return this;
}
The result of
console.log('this is the id: ', id.toString());
is:
this is the id: 621e826f90e8bf45a3fe493d
And the result of
console.log(stock.id);
Is also the same:
621e826f90e8bf45a3fe493d
But when I check the database I see the saved document like below:
{"_id":{"$oid":"621e7fc2f5b14f28463f289f"},"id":"","open":"2660.7250","high":"2660.7250","low":"2660.5700","close":"2660.5700","volume":"1826"}
It seems the line stock.id = id.toString(); doesn't work and it can not put the id into the id property of the stock.
Also when I try to remove the _id property of the saved stock using this line of the code:
delete stock._id;
It gives me this error:
Property '_id' does not exist on type 'Stock'.deno-ts(2339)
What is the problem and how can I resolve it?
EDIT: stock is an instance of Stock class that loads data of a stock using alpha vantage library.
stock.ts:
import { stockCollection } from "../mongo.ts";
import BaseModel from "./base_model.ts";
export default class Stock extends BaseModel {
public id: string = "";
public open: string = "";
public high: string = "";
public low: string = "";
public close: string = "";
public volume: string = "";
constructor({ id = "", open = "", high = "", low = "" ,close = "", volume = "" }) {
super();
this.id = id;
this.open = open;
this.high = high;
this.low = low;
this.close = close;
this.volume = volume;
}
static async findOne(params: object): Promise<Stock | null> {
const stock = await stockCollection.findOne(params);
if (!stock) {
console.log('there is no stock');
return null;
}
console.log('there is a stock');
return new Stock(Stock.prepare(stock));
}
async save(stock : Stock) {
const id = await stockCollection.insertOne(stock);
console.log('this is the id: ', id.toString());
stock.id = id.toString();
console.log(stock.id);
// delete stock._id;
return this;
}
}
BaseModel.ts:
export default class BaseModel {
public static prepare(data: any) {
data.id = data._id.toString();
// delete data._id;
return data;
}
}
Here I am trying to save one instance of a stock in database:
export const stockSave = async () => {
const YOUR_API_KEY = '***********';
const alpha = new AlaphaVantage('***********');
const writestock = await alpha.stocks.intraday('GOOG' , '1min' ).then((data: any) => {return data["Time Series (1min)"]["2022-02-28 14:31:00"]} );
console.log('this is writestock' , writestock);
const stock = new Stock({
id: "",
open: writestock["1. open"],
high: writestock["2. high"],
low: writestock["3. low"],
close: writestock["4. close"],
volume: writestock["5. volume"]});
await stock.save(stock);
}
Edit2: This is the whole project code: https://github.com/learner00000/back
Because you don't instantiate each instance of Stock with an actual ID, and you are instead relying on the ID to be generated by MongoDB, you should not store the id property in the database. You'll probably need to update your schema to reflect this.
You haven't shown a reproducible example with all of the imports and data, so I'm left to guess a bit about some types, but you can refactor the relevant parts of the class like this:
TS Playground
const stockDataKeys = ['close', 'high', 'low', 'open', 'volume'] as const;
type StockDataKey = typeof stockDataKeys[number];
type StockData = Record<StockDataKey, string>;
class Stock extends BaseModel implements StockData {
public id: string;
public close: string;
public high: string;
public low: string;
public open: string;
public volume: string;
constructor (init: Partial<StockData & { id: string; }> = {}) {
super();
this.id = init.id ?? '';
this.close = init.close ?? '';
this.high = init.high ?? '';
this.low = init.low ?? '';
this.open = init.open ?? '';
this.volume = init.volume ?? '';
}
getData (): StockData {
const data = {} as StockData;
for (const key of stockDataKeys) data[key] = this[key];
return data;
}
hasId (): boolean {
return Boolean(this.id);
}
async save (): Promise<void> {
const data = this.getData();
const id = await stockCollection.insertOne(data);
this.id = id.toString();
}
}

Method must be called after React Context render

I've encountered an issue while using React Context. I use something like this to get the selected answer from the context:
const [selectedAnswer, setSelectedAnswer] = React.useState<boolean>(
isSelectedAnswer()
);
function isSelectedAnswer(): boolean {
return QuestionContext!
.getAnswers()
[QuestionContext!.getQuestionIndex(heading)].find(
({ name, value }) => name === heading && value === true
)
? true
: false;
}
But I receive a strange error:
TypeError: this.state.questions.find(...) is undefined
While typeof this.state.questions.find is function, obviously.
I believe the issue is happening, because the QuestionContext's React Component, and in the useState function it's not ready declared. I've used Profiler function, with onRender callback, but I realised that the Profiler function won't be a good candidate for that type of error. But yeah, it worked fine. Anyone knows how to fix that issue?
EDIT: Here's the Context code, which is returning error with the state.
import React, { Component } from "react";
import {
Question,
Questions,
Answers,
Answer,
ParsedAnswer,
Preset,
Presets,
} from "../../typings";
import QuestionsContext from "../QuestionsContext";
import questions from "../../data/questions";
import presets from "../../data/presets";
type QuestionsContextProviderProps = {
children: any;
};
export type QuestionsContextProvidedState = {
questions: Questions;
presets: Presets;
categories: string[];
answers: Answers;
selectedPreset: Preset | null;
getPresets: () => Presets;
getPresetForQuestion: (
questionCategoryIndex: number,
questionIndex: number
) => string;
setPreset: (preset: Preset) => void;
setPresetsInAnswers: (preset: Preset) => void;
getSelectedPreset: () => Preset | null;
getQuestions: () => Questions;
getFieldNameCategory: (fieldName: string) => string;
getQuestionIndex: (fieldName: string) => number;
getQuestionsFromIndex: (index: number) => Question[];
getAnswers: () => Answers;
sendNewAnswer: (
questionIndex: number,
fieldName: string,
answer: string | boolean
) => void | boolean | string;
parseAnswers: () => string;
};
export default class QuestionsContextProvider extends Component<
QuestionsContextProviderProps,
QuestionsContextProvidedState
> {
public readonly state: QuestionsContextProvidedState = {
questions: questions,
presets: presets,
categories: [],
answers: [],
selectedPreset: null,
getPresets: this.getPresets.bind(this),
getPresetForQuestion: this.getPresetForQuestion.bind(this),
setPreset: this.setPreset.bind(this),
setPresetsInAnswers: this.setPresetsInAnswers.bind(this),
getSelectedPreset: this.getSelectedPreset.bind(this),
getQuestions: this.getQuestions.bind(this),
getFieldNameCategory: this.getFieldNameCategory.bind(this),
getQuestionIndex: this.getQuestionIndex.bind(this),
getQuestionsFromIndex: this.getQuestionsFromIndex.bind(this),
getAnswers: this.getAnswers.bind(this),
sendNewAnswer: this.sendNewAnswer.bind(this),
parseAnswers: this.parseAnswers.bind(this),
};
constructor(props: any) {
super(props);
const endingAnswersArray = this.state.answers;
const endingCatagoriesArray = this.state.categories;
const alreadyUsedKeys: string[] = [];
this.state.questions.forEach(({ key }) => {
if (alreadyUsedKeys.find((value) => value === key)) return;
endingAnswersArray.push([]);
endingCatagoriesArray.push(key);
alreadyUsedKeys.push(key);
});
this.state.answers = endingAnswersArray;
this.state.categories = endingCatagoriesArray;
}
public getPresets(): Presets {
return this.state.presets;
}
public getPresetForQuestion(
questionCategoryIndex: number,
questionIndex: number,
preset?: Preset
): string {
return this.state.questions[questionCategoryIndex].values[questionIndex]
.presets[preset || this.state.selectedPreset!];
}
public setPreset(preset: Preset): void {
this.setState({
selectedPreset: preset,
});
}
private getDefaultValueForQuestion(
questionCategoryIndex: number,
questionIndex: number
): string {
return this.state.questions[questionCategoryIndex].values[questionIndex]
.defaultValue;
}
public setPresetsInAnswers(preset: Preset): void {
this.state.categories.forEach((_, key) => {
this.state.questions.forEach(({ values }, idx) => {
values.forEach(({ name }, questionKey) => {
const selectedPreset = this.getPresetForQuestion(
idx,
questionKey,
preset
);
let newPreset: string | boolean = selectedPreset;
if (selectedPreset === "true") newPreset = true;
if (selectedPreset === "false") newPreset = false;
if (
selectedPreset === this.getDefaultValueForQuestion(idx, questionKey)
)
return;
this.sendNewAnswer(key, name, newPreset);
});
});
});
}
public getSelectedPreset(): Preset | null {
return this.state.selectedPreset;
}
public getQuestions(): Questions {
return this.state.questions;
}
public getFieldNameCategory(fieldName: string): string {
return this.state.questions.find(({ values }) =>
values.find(({ name }) => name === fieldName)
)!.key;
}
public getQuestionIndex(fieldName: string): number {
return this.state.categories.findIndex(
(value) => value === this.getFieldNameCategory(fieldName)
);
}
public getQuestionsFromIndex(index: number): Question[] {
return this.state.questions[index].values;
}
public getAnswers(): Answers {
return this.state.answers;
}
public sendNewAnswer(
questionIndex: number,
fieldName: string,
answer: string | boolean
): void | boolean | string {
const alreadyAnsweredField = this.state.answers[questionIndex].find(
({ name }) => name === fieldName
);
if (alreadyAnsweredField) {
return (alreadyAnsweredField.value = answer);
}
const endingAnswersField = this.state.answers;
endingAnswersField[questionIndex].push({
name: fieldName,
value: answer,
});
this.setState({
answers: endingAnswersField,
});
}
public parseAnswers(): string {
const parsedAnswer: ParsedAnswer = {};
this.state.categories.forEach((value, key) => {
parsedAnswer[value] = {};
this.getAnswers()[key].forEach((answer: Answer) => {
parsedAnswer[value][answer.name] = answer.value;
});
});
return JSON.stringify(parsedAnswer, null, 2);
}
public render() {
return (
<QuestionsContext.Provider value={this.state}>
{this.props.children}
</QuestionsContext.Provider>
);
}
}

Why is my object type not getting updated?

I'm creating a permission service using react typescript and I ran into the following problem. I have the class:
import {IPermission} from "../interfaces/IPermission";
class PermissionService {
private permissions: IPermission[] = [];
constructor(permissions: IPermission[]) {
this.permissions = permissions;
}
public getValue(key: string): IPermission['value'] {
const perm = this.permissions.find(permission => permission.key === key);
if (!perm) {
throw new Error('Could not find the permission');
}
return perm.value;
}
public modifyPermission(key: string, defaultValue: any, value: any): void {
const perms = [...this.permissions];
for (let i = 0; i < perms.length; i++) {
perms[i].defaultValue = defaultValue;
perms[i].value = value
}
this.permissions = perms;
console.log(perms);
}
public parseActivePermissions(permissions: IPermission[]): IPermission[] {
this.permissions.forEach(permission => {
permissions.forEach(activePermission => {
if (permission.key === activePermission.key) {
permission.defaultValue = activePermission.defaultValue;
permission.value = activePermission.value;
}
})
})
return this.permissions;
}
public getAll(): IPermission[] {
return this.permissions;
}
}
export default PermissionService;
and an AdminPermissions data file
import PermissionService from "../services/permission.service";
import {IPermission} from "../interfaces/IPermission";
import Permissions from "./Permissions";
const service: PermissionService = new PermissionService(Permissions);
service.modifyPermission('canAccessAcp', true, true);
const AdminPermissions: IPermission[] = service.getAll();
export default AdminPermissions;
The problem is, the service.modifyPermission() does not update the defaultValue and value of the permission. It's still false when console logging. Why is that?
UPDATE #1
Changed the file a bit. Still doesn't work. Now I'm directly changing the values, but they still log as false.
class AdminPermissions {
public getAll(): IPermission[] {
const service: PermissionService = new PermissionService(Permissions);
service.permissions.forEach(permission => {
if (permission.key === 'canAccessAcp') {
permission.defaultValue = true;
permission.value = true;
}
})
return service.permissions;
}
}
The problem is that with forEach you are not changing the actual value of each items, so you should do something like this:
class AdminPermissions {
public getAll(): IPermission[] {
const service: PermissionService = new PermissionService(Permissions);
return service.permissions.map(permission => {
if (permission.key === 'canAccessAcp') {
return (
{
...permission,
defaultValue: true,
value: true
}
)
}
return permission
});
}
}
I found a solution.
In the permission.service.ts
public modifyPermission(key: string, defaultValue: any, value: any): void {
const perms: IPermission[] = this.permissions.map(permission => {
if (permission.key === key) {
console.log('found permission');
return {
...permission,
defaultValue,
value
}
}
return permission
})
this.permissions = perms;
}

TypeError: Cannot read property 'length' of null in react component

Okay so I've gotten pretty far in creating the React Component for ChartJS, however when testing I get the following error:
FAIL lib\chart\chart.test.tsx
● renders without crashing
TypeError: Cannot read property 'length' of null
at Object.acquireContext (node_modules/chart.js/src/platforms/platform.dom.js:189:19)
at Chart.construct (node_modules/chart.js/src/core/core.controller.js:72:27)
at new Chart (node_modules/chart.js/src/core/core.js:7:8)
at Chart.Object.<anonymous>.Chart.renderChart (lib/chart/chart.tsx:233:26)
at Chart.Object.<anonymous>.Chart.componentDidMount (lib/chart/chart.tsx:42:10)
at node_modules/react-dom/lib/ReactCompositeComponent.js:264:25
at measureLifeCyclePerf (node_modules/react-dom/lib/ReactCompositeComponent.js:75:12)
at node_modules/react-dom/lib/ReactCompositeComponent.js:263:11
at CallbackQueue.notifyAll (node_modules/react-dom/lib/CallbackQueue.js:76:22)
at ReactReconcileTransaction.close (node_modules/react-dom/lib/ReactReconcileTransaction.js:80:26)
at ReactReconcileTransaction.closeAll (node_modules/react-dom/lib/Transaction.js:209:25)
at ReactReconcileTransaction.perform (node_modules/react-dom/lib/Transaction.js:156:16)
at batchedMountComponentIntoNode (node_modules/react-dom/lib/ReactMount.js:126:15)
at ReactDefaultBatchingStrategyTransaction.perform (node_modules/react-dom/lib/Transaction.js:143:20)
at Object.batchedUpdates (node_modules/react-dom/lib/ReactDefaultBatchingStrategy.js:62:26)
at Object.batchedUpdates (node_modules/react-dom/lib/ReactUpdates.js:97:27)
at Object._renderNewRootComponent (node_modules/react-dom/lib/ReactMount.js:319:18)
at Object._renderSubtreeIntoContainer (node_modules/react-dom/lib/ReactMount.js:401:32)
at Object.render (node_modules/react-dom/lib/ReactMount.js:422:23)
at Object.<anonymous> (lib/chart/chart.test.tsx:7:12)
at Promise (<anonymous>)
at Promise.resolve.then.el (node_modules/p-map/index.js:42:16)
at <anonymous>
at process._tickCallback (internal/process/next_tick.js:169:7)
× renders without crashing (275ms)
Test Suites: 1 failed, 1 total
Tests: 1 failed, 1 total
Snapshots: 0 total
Time: 1.314s, estimated 3s
Ran all test suites related to changed files.
However, I've spent a long time looking over the code and haven't been able to figure out why it refuses to work properly. The error starts at the renderChart() function at creating a new chart instance. My first guess would be the for some reason it's not registering the canvas element despite being called by its id. But when the content of renderChart is moved into the render() function it still gives the same error. Here's the code being tested:
import * as React from 'react'
import * as ClassNames from 'classnames'
import * as ChartJS from 'chart.js'
const IsEqual = require('lodash.isequal')
const Find = require('lodash.find')
const subChart = require('chart.js')
interface IChartProps {
/** The user-defined classes */
readonly className?: string
readonly width?: number
readonly height?: number
readonly reRender?: boolean
readonly type: ChartJS.ChartType
readonly data: ChartJS.ChartData
readonly options: ChartJS.ChartOptions
readonly getDatasetAtEvent?: Function
readonly getElementAtEvent?: Function
readonly getElementsAtEvent?: Function
readonly onElementsClick?: Function
readonly datasetKeyProvider?: Function
}
interface IChartState {
/** Add your states here */
}
export class Chart extends React.Component<IChartProps, IChartState> {
// tslint:disable-next-line
private chartInstance: any
private shadowData: {}
constructor(props: IChartProps) {
super(props)
}
public componentWillMount() {
// this.chartInstance = undefined
}
public componentDidMount() {
this.renderChart()
}
// public componentWillReceiveProps(nextProps: IChartProps) {}
public shouldComponentUpdate(nextProps: IChartProps, nextState: IChartState) {
const props = this.props
if (nextProps.reRender === true) {
return true
}
if (props.height !== nextProps.height || props.width !== nextProps.width) {
return true
}
if (props.type !== nextProps.type) {
return true
}
if (!IsEqual(props.options, nextProps.options)) {
return true
}
const nextData = this.transformDataProp(nextProps)
if (!IsEqual(this.shadowData, nextData)) {
return true
}
return false
}
// public componentWillUpdate(nextProps: IChartProps, nextState: IChartState) {}
public componentDidUpdate(prevProps: IChartProps, prevState: IChartState) {
if (this.props.reRender) {
this.chartInstance.destroy()
this.renderChart()
return
}
this.updateChart()
}
public transformDataProp(props: IChartProps) {
const data = props.data
if (typeof data === 'function') {
const node = document.getElementById('bar-chart') as HTMLCanvasElement
return data(node)
} else {
return data
}
}
public memoizeDataProps(props?: IChartProps) {
if (!this.props.data) {
return
}
const data = this.transformDataProp(this.props)
this.shadowData = {
...data,
datasets:
data.datasets &&
data.datasets.map((set: string[]) => {
return { ...set }
})
}
return data
}
public updateChart() {
const options = this.props.options
const data = this.memoizeDataProps(this.props)
if (!this.chartInstance) {
return
}
if (options) {
this.chartInstance.options = subChart.helpers.configMerge(
this.chartInstance.options,
options
)
}
let currentDatasets =
(this.chartInstance.config.data &&
this.chartInstance.config.data.datasets) ||
[]
const nextDatasets = data.datasets || []
const currentDatasetKeys = currentDatasets.map(
this.props.datasetKeyProvider
)
const nextDatasetKeys = nextDatasets.map(this.props.datasetKeyProvider)
const newDatasets = nextDatasets.filter(
(d: object) =>
currentDatasetKeys.indexOf(this.props.datasetKeyProvider(d)) === -1
)
for (let idx = currentDatasets.length - 1; idx >= 0; idx -= 1) {
const currentDatasetKey = this.props.datasetKeyProvider(
currentDatasets[idx]
)
if (nextDatasetKeys.indexOf(currentDatasetKey) === -1) {
// deleted series
currentDatasets.splice(idx, 1)
} else {
const retainedDataset = Find(
nextDatasets,
(d: object) => this.props.datasetKeyProvider(d) === currentDatasetKey
)
if (retainedDataset) {
// update it in place if it is a retained dataset
currentDatasets[idx].data.splice(retainedDataset.data.length)
retainedDataset.data.forEach((point: number, pid: number) => {
currentDatasets[idx].data[pid] = retainedDataset.data[pid]
})
// const { data, ...otherProps } = retainedDataset
currentDatasets[idx] = {
data: currentDatasets[idx].data,
...currentDatasets[idx],
...retainedDataset.otherProps
}
}
}
}
// finally add any new series
newDatasets.forEach((d: object) => currentDatasets.push(d))
const { datasets, ...rest } = data
this.chartInstance.config.data = {
...this.chartInstance.config.data,
...rest
}
this.chartInstance.update()
}
public componentWillUnmount() {
this.chartInstance.destroy()
}
public onClickEvent = (event: React.MouseEvent<HTMLCanvasElement>) => {
// this.props.getDatasetAtEvent &&
this.props.getDatasetAtEvent(
this.chartInstance.getDatasetAtEvent(event),
event
)
// this.props.getElementAtEvent &&
this.props.getElementAtEvent(
this.chartInstance.getElementAtEvent(event),
event
)
// this.props.getElementsAtEvent &&
this.props.getElementsAtEvent(
this.chartInstance.getElementsAtEvent(event),
event
)
// this.props.onElementsClick &&
this.props.onElementsClick(
this.chartInstance.getElementsAtEvent(event),
event
)
}
public render() {
const className = ClassNames('chart', this.props.className)
// bar.update()
return (
<div className={className}>
<canvas
id="chart-instance"
width={this.props.width ? this.props.width : '400'}
height={this.props.height ? this.props.height : '400'}
onClick={this.onClickEvent}
/>
</div>
)
}
public renderChart() {
const { options, type, data } = this.props
const node = document.getElementById('chart-instance') as HTMLCanvasElement
// const data = this.memoizeDataProps()
this.chartInstance = new ChartJS(node, {
type,
data,
options
})
}
}
can someone help me figure out why this won't work properly?
It might because of this:
currentDatasets[idx].data.splice(retainedDataset.data.length)
You should have a check on retainedDataset.data also:
if (retainedDataset && retainedDataset.data) { ... }

Resources