I'm trying to build an app using react native with a firestore database. I'm fairly new to the react native framework (as well as working with firestore), so it's possible I might be trying to solve this problem the wrong way.
I have a database that works well and is already populated. For each user of this app, I'd like to add a map to their entry. I want to use this map to store some data about the user which they can fill out later.
Here's some code:
componentDidMount() {
this.readProfile(this.props.uid);
}
readProfile = (uid) => {
this.props.getProfile(uid).then((profile) =>
{
if(!profile.userMap)
{
profile.userMap = generateUserMap();
}
...
}
export const generateUserMap = function () {
var map = new Map();
SomeEnum.forEach((key, value) => {
map.set(key, false);
});
AnotherEnum.forEach((key, value) => {
map.set(key, false);
});
OneMoreEnum.forEach((key, value) => {
map.set(key, false);
});
return map;
};
...
<Input
value={this.state.profile.userMap[SomeEnum.Foo]}
onChangeText={(foo) => this.updateUserMap({ foo })}
/>
What I want this code to be doing is to read in the user's profile when I load the page. That part seems to be working fine. My next concern is to properly initialize the map object. The code doesn't seem to be properly initializing the map, but I'm not sure why. Here's why I say that:
TypeError: Cannot read property 'Foo' of undefined
With the stack trace pointing to my component's Connect() method.
Any help would be greatly appreciated.
EDIT: Apologies for the oversight, here is the updateUserMap function:
updateUserMap = (property) => {
const profile = Object.assign({}, this.state.profile, property);
this.setState({ profile });
}
So, as anyone who looks over this question can probably tell, I was doing a few things pretty wrong.
The error I'm getting referred specifically to that input block in my render method - this.state.profile.userMap was undefined at that point. I can guarantee that it won't be undefined if I do my check within the render method but before I'm accessing the userMap. Because of how the lifecycle methods work in react native, ComponentDidMount wouldn't be called before my render method would.
My enum code also wouldn't work. I changed that to a simple for loop and it works like a charm.
Here's my updated code:
render() {
if(!this.state.profile.userMap)
{
this.state.profile.userMap = generateUserMap();
}
Related
I'm trying to make some async actions with redux toolkit in react-native. The project runs on redux without any issues, beside the implementation issues for createAsyncThunk.
I used the same logic as described in the docs
Within my Slice, I'm creating the createAsyncThunk Object as follows:
export const fetchAddressList = createAsyncThunk('/users/fetchAddresses', async(thunkAPI) => {
const state = thunkAPI.getState();
console.log("THUNK state.loggedIn: "+state.loggedIn);
if(state.loggedIn){
return apiHelper.getAddressDataAsync();
}
});
It only differs in the export tag before const tag compared to the docs. I had to make it in order to access the fetchAddressList from outside. The apiHelper.getAddressDataAsync() is an async method, that returns the result of a fetch.
Than I added the extraReducers attribute to my slice object.
export const appDataSlice = createSlice({
name: "appDataReducer",
initialState:{
//Some initial variables.
},
reducers: {
//Reducers...
},
extraReducers: (builder) => {
builder.addCase(fetchAddressList.fulfilled, (state, action) => {
console.log("FULLFILLED::: ",action.payload);
state.addressList = action.payload.addressList;
state.defaultAddressId = action.payload.defaultAddressId;
})
}
});
export const { /*REDUCER_METHOD_NAMES*/ } = appDataSlice.actions;
This slice is stored in the store using configureStore, among other slices, that are definitely working fine.
Calling the fetchAddressList() method using dispatch doesn't do anything:
dispatch(fetchAddressList());
What exactly am I doing wrong here? Would appreciate any hints.
Edit:
Are there configurations required within the configureStore()-method when creating the store object?
This is how I create the store object:
export const store = configureStore({
reducer: {
/*Other reducer objects....,*/
appDataReducer: appDataSlice.reducer
},
});
Maybe something is missing here...
It was due to wrong usage of the createAsyncThunk()-method. I'd passed the thunkAPI to be as the first (and only) parameter to the inner method, which was linked to user arguments passed through parameters into the initial dispatch method (like dispatch(fetchAddressList("ARG_PASSED_TO_FIRST_PARAMETER_OF_ASNYCTHUNK"));). However thunkAPI is being injected into the second parameter of createAsyncThunk()-method and as a result thunkAPI was undefined, since I hadn't passed any parameters by calling dispatch(fetchAddressList());
It was odd, to not have any errors / exceptions
calling a method of an undefined object though => thunkAPI.getState().
The solution is to use the second parameter for thunkAPI.
You do have two options by doing so.
1 - Either load the whole thunkAPI into the second parameter and use it as so:
export const fetchAddressList = createAsyncThunk('/users/fetchAddresses', async(args, thunkAPI) => {
console.log("TEST: ", thunkAPI.getState());
thunkAPI.dispatch(...);
});
2 - Or load exported methods by the thunkAPI:
export const fetchAddressList = createAsyncThunk('/users/fetchAddresses', async(args,{getState, dispatch}) => {
console.log("TEST: ", getState());
dispatch(...);
});
Both ways will work. Happy coding :)
I am working on a Chat Room portion of a larger React Native app and am facing issues with the updating of the page after a text has been sent. Currently, when the user compiles a text in a TextInput and hits the send button, it triggers a mutation that is supposed to add a message object to the chatroom model, which is linked to all of the users that are currently in the chatroom. It is then supposed to take the result from this mutation, which is the updated chatroom connected to all the users (the current user included obviously) and render its contents. It is intended to rerender the page after the activeThread atom is updated, since the page used the contents of activeThread to render everything on the page, new messages included. However, this occurs asyncronously and it tries to render a promise.... which you can't do. I've tried everything I'm capable of, using thens and awaits everywhere I could but JavaScript's giving me the middle finger pretty hard on this one. My code is below...
const handleSendMessage = async () => {
console.log(activeThread.id)
if (newMessage.length > 0){
return sendMessage({
variables: {
chatroomId: activeThread.id,
content: newMessage
}
}).then( async (newMessageThread) => {
await setUpdating(true)
await setNewMessage("")
await setKeyboardVisible(false);
await setActiveThread(newMessageThread)
}).then( async() => {
await console.log(activeThread)
await setUpdating(false)
})
}
else{
}
}
setUpdating is part of a useState. This is defaulted to false and when true the main page is not set to render. It is intended as a guard against attempting to render the promise. Didn't work, obviously
setNewMessage is defaulted to "" and is responsible for keeping track of the text the user has entered into the TextInput. Pretty irrelevant here.
setKeyBoardVisible is pretty self explanatory and also not necessary
setActiveThread is the heavy lifter here. Pretty much all of the contents rendered are going to be pulling data from activeThread, which is, again; a recoil state. For example, everything below looks essentially something like
<View>
<Text> {activeThread.someMethodOrValue} </Text>
</View>
I can only assume this has something to do with the async-ing. I have a console.log(error) statement in my backend GraphQL mutation resolver that would catch any errors there, and it's not triggering anything. The error I get everytime is the following...
TypeError: undefined is not an object (evaluating 'activeThread.chatroomName.split')
This error is located at:
in MessageThread (created by SceneView)
in StaticContainer
in EnsureSingleNavigator (created by SceneView)
in SceneView (created by SceneView)
in {/* keeps going down the stack you get the idea */}
[Unhandled promise rejection: TypeError: undefined is not an object (evaluating 'activeThread.chatroomName.split')]
at Pages/CommunicationPage/MessageThread.js:210:37 in MessageThread
Any solutions?
While the code I had still looks like it should work to me, we all know how finnicky code can be sometimes. What ended up working was separating the handleSendMessage function and the mutation, creating a whole new function for the mutation.
My new code looks like this...
const handleSendMessage = () => {
if (newMessage.length > 0){
handleMutation().then( (resolved) => { // This line fixed all the promise issues
setNewMessage("") // clears current message input
let newActiveThread = resolved.data.driverSendMessage.chatroom // creates new thread JSON from mutation data
console.log(newActiveThread) // testing out a different bug now lolllll
setActiveThread(newActiveThread) // Sets current thread to match the new one
// changes the entire user state, leaving all over threads untouched but updating the current one
let updatedThreads = [newActiveThread]
user.chatrooms.forEach( (chat) => {
if (chat.id == newActiveThread.id){
console.log("Skipping " + newActiveThread.chatroomName)
}
else {
updatedThreads.push(chat)
}
})
// changes the main recoil state
setUser({...user, chatrooms: updatedThreads})
})
}
else{
// Throw Error Handling for no input or just do nothing, we'll see
}
}
const handleMutation = async () => {
return sendMessage({
variables: {
chatroomId: activeThread.id,
content: newMessage
}
})
}
I am trying to show some dynamic content in my component but somehow useEffect causes a infinite loop.
What can be the problem?
useEffect(() => {
retrieveLocalData('following').then((contacts) => {
setLocalData(JSON.parse(contacts));
});
}, [getLocalData]);
async function retrieveLocalData(key) {
try {
return await AsyncStorage.getItem(key);
} catch (error) {
console.log(error);
}
}
console.log('test'); // infinite
Code: https://codepen.io/eneskul/pen/OJWEgmw
Updated Answer
The infinite loop is a result of the useEffect hook updating the same value that is triggering the hook to run in the first place.
Here's a simple example to illustrate the problem:
const [value, setValue] = useState({ foo: 'bar' });
useEffect(() => {
Promise.resolve('{"foo":"bar"}').then((result) => {
const newValue = JSON.parse(result);
// `newValue` is a new object, even if its content is identical to `value`.
setValue(newValue);
});
}, [value]);
In this example, when value is set, it causes the useEffect hook to execute, which will asynchronously update value with a new object, which will cause the useEffect hook to execute again, and so on. Even though the contents of the objects are identical, the JSON.parse call creates a new object with a new reference.
You can prevent the infinite loop by doing a deep equality check of the two objects before updating the state. Using something like Lodash's isEqual function makes this pretty easy.
useEffect(() => {
Promise.resolve('{"foo":"bar"}').then((result) => {
setValue((prev) => {
const newValue = JSON.parse(result);
// Do a deep comparison and only update state with new object if content is different.
return isEqual(prev, newValue) ? prev : newValue;
});
});
}, [value]);
In this example, the reference to value will only change if the contents of the objects are different.
However, this only explains what the problem is. I'm not sure what the right solution is for your problem, since it's not clear why the component only needs to load data from local storage into state when the state changes, but the state is only updated when it loads from local storage. There seems to be a "chicken or the egg" problem here. It feels like there should be something else that should trigger loading data from local storage into state, other than the data that was just loaded from local storage into state.
Previous Answer
The likely culprit here is getLocalData in the dependency list of the useEffect hook. If that is not a stable reference (i.e. the reference changes on each render), then it will cause the useEffect hook to execute, which will then trigger a state update, which will trigger a render, which will cause useEffect to execute again, which starts the whole thing over again.
In the sample code, it's not clear where getLocalData comes from. Wherever it comes from, you might consider wrapping it with the useCallback hook to create a stable reference. If it's just a typo and meant to be retrieveLocalData, then that is definitely the issue. Because retrieveLocalData is declared inside the component's render function, it will create a new instance of the function (with a new reference) on each render.
I would just move it inside the useEffect hook and eliminate the dependencies.
useEffect(() => {
AsyncStorage.getItem('following')
.then((contacts) => {
setLocalData(JSON.parse(contacts));
})
.catch((error) => {
console.log(error);
});
}, []);
I try to display {{ bgColor }} in my template.
But it only displays [object Promise]
const { getColorFromURL } = require('color-thief-node')
export default {
data() {
return {
api_url: process.env.strapiBaseUri,
bgColor: this.updateBgColor(this.project),
}
},
props: {
project: Object,
},
methods: {
updateBgColor: async function(project){
return await getColorFromURL(process.env.strapiBaseUri+this.project.cover.url).then((res) => {
const [r, g, b] = res
console.log( `rgb(${r}, ${g}, ${b})` )
return `rgb(${r}, ${g}, ${b})`
})
}
}
}
The function looks to work, as I get the result on console.log
rgb(24, 155, 97)
I think I'm lost between methods, the plugin and the use of an async function.
Any help appreciated !
https://forum.vuejs.org/t/render-async-method-result-in-template/36505/3
Render is sync. You should call this method from created or some other suitable lifecycle hook and save the result in component’s data, then render that data.
If not lifecycle any event like 'click' will be good also
The best thing you probably can do is to change return statement to assign statement
Instead of return 'rgb(${r}, ${g}, ${b})' try this.something = rgb(${r}, ${g}, ${b})' (of course you have to define something in data object). Then just render {{ something }} in your template. Reactivity system will do the rest
Soo all your Vue logic is good, but promise is never resolved cause of :
canvas-image.js?14a5:35 Uncaught (in promise) DOMException: Failed to execute 'getImageData' on 'CanvasRenderingContext2D': The canvas has been tainted by cross-origin data.
I've copied it to my local project and I am getting this error
How to fix getImageData() error The canvas has been tainted by cross-origin data?
You won't be able to draw images directly from another server into a canvas and then use getImageData. It's a security issue and the canvas will be considered "tainted".
Don't know if it will solve your problem but you can download this image. And put to for example inside assets. Then just required it from there. And it will work
I am stuck with this error:
Cannot add a child that doesn't have a YogaNode to a parent without a measure function!
(Trying to add a 'ReactRawTextShadowNode' to a 'LayoutShadowNode')
The app is on expo and works fine in iOS
but on Android I always get this error when pressing the button for authentication.
Earlier it was working fine, I tried to reset my commits to track the error but for no help.
I think, whenever this function is executed, the error arises:
onButtonPress = async () => {
const { code } = this.props;
await this.props.loginUser({ code });
if (this.props.error) {
await AsyncStorage.removeItem('code');
this.props.navigation.goBack();
} else {
await AsyncStorage.setItem('code', code);
await this.props.orderUpdate();
await this.props.menuFetch();
this.props.navigation.navigate('main');
}
};
Note that the props are accessing redux state and calling redux actions.
This issue having a different reason:
Might be the comments inside the render method of component so try to remove comments inside render method of component component.
Might be because of that you have not closed a tag correctly
Might be using of && operator inside render method so remove '&&'
operator and use ternary operator.
Instead { foobar && <View/> }
Use this { foobar ? <View/> : null }