So I'm trying recoilJS for a js game that I am building and it pretty neat, but the need to update atoms from components only feels like a limitation.
To create a game loop, I put all the logic on empty component so I will be able to read and write states. Even if I will construct the login outside of the component, I will need especially move different stats around all the time. There is a way to update atoms outside of react component (not via hooks)?
I use RXJS to help to set RecoilJS value outside of the component.
At the start, I created 4 parts as
Main component
RecoilJS component
Atom file
set RecoilJS outside value of the component file
1).Main
import React from 'react';
import {
RecoilRoot
} from 'recoil';
function App() {
return (
<RecoilRoot>
<MainScreens />
<RecoilJSComponent/>
</RecoilRoot>
);
}
2).RecoilJS component
import React from 'react';
import {
useRecoilCallback
} from 'recoil';
import { Subject } from 'rxjs';
export const setRecoil = new Subject();
const getRecoil = new Subject();
const returnRecoil = new Subject();
export const promiseGetRecoil = (recoilObj) => {
return new Promise(async (resolve, reject) => {
getRecoil.next(recoilObj)
returnRecoil.subscribe({
next: (value) => {
if (recoilObj === value.recoilObj) {
resolve(value.value)
}
}
});
})
}
export default function RecoilJSComponent() {
const setStore = useRecoilCallback(({ set }) => (n) => {
set(n.recoilObj, () => (n.value));
}, [])
const getStore = useRecoilCallback(({ snapshot }) => async (recoilObj) => {
const valueRecoilObj = await snapshot.getPromise(recoilObj);
returnRecoil.next({ recoilObj: recoilObj, value: valueRecoilObj })
}, [])
setRecoil.subscribe({
next: (value) => {
setStore(value)
}
});
getRecoil.subscribe({
next: (recoilObj) => {
getStore(recoilObj)
}
});
return null;
}
3).Atom file
export const textState = atom({
key: 'textState'
default: ''
});
4).set RecoilJS outside the value of the component file
import API from './Api';
import { setRecoil } from './RecoilJSComponent'
import { textState } from './textState'
export const setValueReCoil = () => {
API()
.then(result => {
setRecoil({ recoilObj: textState, value: result })
})
.catch(ex => {
})
};
The main idol is in 2 and 4
In number 2,
I create to use RXJS for setting value via the component and I export RXJS to set a value on RecoilJS outside of the component
I hope my idol can help you to resolve your problem
There isn't right now. Opened a suggestion for recoil team.
Related
I have established a websocket connect from my server to my client machine. I have parsed the data into an object and would like to access the data for representation on my front end.
import './App.css';
import { w3cwebsocket as W3CWebSocket } from "websocket";
import { Component } from 'react';
const client = new W3CWebSocket('ws://xyz:9080/user');
class App extends Component {
componentDidMount() {
client.open = () => {
console.log("Connected");
};
client.onmessage = (e) => {
const object = JSON.parse(e.data);
console.log(object.Snapshot);
}
client.onclose = () => {
console.log("Closed...");
}
}
render() {
return (<div className="App">
<h2>{ object }</h2>
</div>
);
}
}
export default App;
I want to access my object variable from the on message function and use it as a variable in my render function. How do I approach this?
You need to add local state to your class. State is a fairly foundational part of react and how it is able to reactively rerender components, so it sounds like you need to spend some time reading the docs to familiarize yourself with the basics.
That said, I'll provide an updated version of your code for demonstration purposes. Note that you used client.open when you meant client.onopen, so I've made that correction below:
import "./App.css";
import { w3cwebsocket as W3CWebSocket } from "websocket";
import { Component } from "react";
const client = new W3CWebSocket("ws://xyz:9080/user");
class App extends Component {
constructor(props) {
super(props);
this.state = { object: "" };
}
componentDidMount() {
client.onopen = () => {
console.log("Connected");
};
client.onmessage = (e) => {
const object = JSON.parse(e.data);
this.setState({ object: object });
console.log(object.Snapshot);
};
client.onclose = () => {
console.log("Closed...");
};
}
render() {
return (
<div className="App">
<h2>{this.state.object}</h2>
</div>
);
}
}
export default App;
Also, since it seems that you're probably just starting out with react, I would strongly recommend that instead of the old-style class-based components, you use learn to use hooks and functional components, which is just an overall much cleaner and easier to reason about way to write react code. We could rewrite your code as follows using the useState and useEffect hooks in an App function:
import "./App.css";
import { w3cwebsocket as W3CWebSocket } from "websocket";
import { useEffect, useState } from "react";
export default function App() {
const [object, setObject] = useState("");
useEffect(() => {
const client = new W3CWebSocket("ws://xyz:9080/user");
client.onopen = () => {
console.log("Connected");
};
client.onmessage = (e) => {
const newObj = JSON.parse(e.data);
setObject(newObj);
console.log(newObj.Snapshot);
};
client.onclose = () => {
console.log("Closed...");
};
return () => client.OPEN && client.close();
}, []);
return (
<div className="App">
<h2>{object}</h2>
</div>
);
}
Note per the docs that useEffect with an empty dependency array is more or less equivalent to componentDidMount. Note also that even though client is defined in a local scope, it won't be garbage-collected, because it is referenced in the cleanup closure (the return value of the arrow function passed to useEffect).
Finally, note that I haven't used the websocket package before, so I don't know if your usage is correct or optimal. This answer is about how to manage state in react, not how to use websocket in a react application.
I have this working in a React Native Class Component, but cannot get it to work in a Function Component. I am reading data from a text file using react-native-fs. The file is read asynchronously and the data is available as a javascript Promise object. First the working code:
import React, { Component } from 'react';
import { Text } from 'react-native';
const RNFS = require('react-native-fs');
const path = `${RNFS.DocumentDirectoryPath}/Quote1.txt`;
RNFS.writeFile(path, 'The price of freedom is eternal vigilance.', 'utf8')
.then(() => {
console.log('FILE WRITTEN!');
});
export default class App extends Component {
constructor(props) {
super(props);
this.state = { myResourceContent: 'not loaded yet' };
}
componentDidMount() {
RNFS.readFile(path, 'utf8').then((content) => {
this.setState({ myResourceContent: content });
console.log(`FILE READ: ${content}`);
});
}
render() {
const { myResourceContent } = this.state;
return (
<Text>{myResourceContent}</Text>
);
}
}
I am able to load the text file and to render it on the screen. But when trying to do the same thing in a in a Function Component, I can read the data from the file (as seen in the log), but I cannot get it out of the Promise object to render the text on the screen. I have read many async-await related answers here and also tried to use React Hooks, but I seem to be missing something foundational. Here is the code I tried:
import React from 'react';
import { Text } from 'react-native';
const RNFS = require('react-native-fs');
const path = `${RNFS.DocumentDirectoryPath}/Quote1.txt`;
RNFS.writeFile(path, 'The price of freedom is eternal vigilance.', 'utf8')
.then(() => {
console.log('FILE WRITTEN!');
});
const mycontent = RNFS.readFile(path, 'utf8').then((content) => {
return content;
});
async function getMyContent() {
const aw = await mycontent;
console.log(`FILE READ: ${aw}`);
return aw;
};
getMyContent();
export default function App() {
const myResourceContent = getMyContent();
console.log(`QUOTE: ${myResourceContent}`);
return (
<Text>'QUOTE: '</Text>
);
}
The async function getMyContent correctly receives the file data, but then I cannot get the text data into the export default function App. This was very easy to do in the class based code above, but here I get a promise object which does not seem to contain the text outside of the async function. The log shows:
LOG QUOTE: [object Object]
LOG FILE WRITTEN!
LOG FILE READ: The price of freedom is eternal vigilance.
Using another approach, when I tried to declare a state variable and attempted to useState inside the async function I receive the error: "Possible Unhandled Promise Rejection (id: 1): Error: Invalid hook call." I understand that I probably need to integrate the useState hook to replicate the class component features:
const [myResourceContent, setContent] = useState(0);
setContent(content);
If this is actually the correct approach, where am I supposed to place this (and any missing) code? If this is not the correct approach what would be the right one?
if I had to write your class that works as a functional component I would do this
import React, { useState, useEffect } from "react";
import { Text } from "react-native";
const RNFS = require("react-native-fs");
const path = `${RNFS.DocumentDirectoryPath}/Quote1.txt`;
RNFS.writeFile(path, "The price of freedom is eternal vigilance.", "utf8").then(
() => {
console.log("FILE WRITTEN!");
}
);
const App = () => {
const [state, setState] = useState({ myResourceContent: "not loaded yet" });
useEffect(() => {
RNFS.readFile(path, "utf8").then((content) => {
setState({ myResourceContent: content });
console.log(`FILE READ: ${content}`);
});
}, []);
return <Text>{state.myResourceContent}</Text>;
};
export default App;
You can have state in function components using hooks:
export default function App() {
const [content, setContent] = useState();
useEffect(() => {
getMyContent().then(result => setContent(result));
});
return (
<Text>{content}</Text>
);
}
Once the content is loaded, the state will be set and the component will re-render with the text.
If you want to use a placeholder while the content is loading, you can set the initial value in useState:
const [content, setContent] = useState("Loading text");
I'm trying to access 2 different stores in a single component, but worry that perhaps the architecture of my app may need to change as easy-peasy may not have this functionality.
I have a GlobalStore
import { createStore } from 'easy-peasy';
const globalModel = {
menuOpen: false,
toggleMenu: action((state, payload) => {
state.menuOpen = payload;
}),
};
const GlobalStore = createStore(globalModel);
export default GlobalStore;
Just for this example, I'll use a single state and action used in the store to define whether the navigation menu is open or not.
The GlobalStore appears at the top level of my app in my App.js file.
import React from 'react';
import { StoreProvider } from 'easy-peasy';
import GlobalStore from './store/GlobalStore';
const App = () => {
return (
<StoreProvider store={GlobalStore}>
</StoreProvider>
);
};
export default App;
Now, further down the tree, I have another store SearchStore that dictates which view is active in the component.
import { createStore } from 'easy-peasy';
import { action } from 'easy-peasy';
const searchModel = {
view: 'filter',
setView: action((state, payload) => {
state.view = payload;
}),
};
const SearchStore = createStore(searchModel);
export default SearchStore;
The issue I have now is that in a component that I need to be able to access both stores to update the view with the setView action in the SearchStore and get the value of menuOpen from the GlobalStore but cannot access both concurrently.
The example I have in a component is that I have a styled component that when clicked calls the action setView but its position is also defined by whether the menuOpen is true or not. but obviously, if I try and get the state of menuOpen it will be undefined as it does not exist in SearchStore
const Close = styled.span`
$(({ menuOpen }) => menuOpen ? `
// styles go here
` : `` }
`;
const setView = useStoreActions((action) => action.setView);
const menuOpen = useStoreState((state) => state.menuOpen);
<Close menuOpen={menuOpen} onClick={() => setView('list')}>
Is this possible? Any help would be much appreciated.
Alternative 1: extending the global store
To access both store (via the useStoreState/Actions from the StoreProvider), you could nest both "sub" stores into the GlobalStore:
// SearchModel.js
import { action } from 'easy-peasy';
const searchModel = {
view: 'filter',
setView: action((state, payload) => {
state.view = payload;
}),
};
export default searchModel;
// MenuModel.js
import { action } from 'easy-peasy';
const menuModel = {
isOpen: false,
toggle: action((state, payload) => {
state.isOpen = !state.isOpen;
}),
};
export default menuModel;
// GlobalStore.js
import { createStore } from 'easy-peasy';
import menu from './MenuhModel';
import search from './SearchModel';
const globalModel = {
menu,
search,
};
const GlobalStore = createStore(globalModel);
export default GlobalStore;
This way, you can access both stores at your convenience, using the hooks:
const searchState = useStoreState((state) => state.search);
const menuState = useStoreState((state) => state.menu);
const searchActions = useStoreActions((action) => action.search);
const menuActions = useStoreActions((action) => action.menu);
Alternative 2: useLocalStore()
If you do not want to extend the global store, you could create a local store, by using the useLocalStore():
function Menu() {
const [state, actions] = useLocalStore(() => ({
isOpen: false,
toggle: action((state, payload) => {
state.isOpen = !state.isOpen;
}),
}));
return (
<div>
{state.isOpen && <MenuItems />}
<button onClick={() => actions.toggle()}>Open menu</button>
</div>
);
}
However, the drawback of this approach, is that the state is not global and only available at the component-level.
You could however get around this, by creating your own provider - but then again, alternative 1 would probably be the path of least resistance.
I'm trying to call an action in my vue from my store.
This is my file aliments.js in my store:
import Vue from 'vue';
import Vuex from 'vuex';
import axios from 'axios';
Vue.use(Vuex, axios);
export const state = () => ({
aliments: {},
})
export const mutations = () => ({
SET_ALIMENTS(state, aliments) {
state.aliments = aliments
}
})
export const actions = () => ({
async getListAliments(commit) {
await Vue.axios.get(`http://localhost:3080/aliments`).then((response) => {
console.log(response);
commit('SET_ALIMENTS', response);
}).catch(error => {
throw new Error(`${error}`);
})
// const data = await this.$axios.get(`http://localhost:3080/aliments`)
// commit('setUser', user)
// state.user = data;
// return state.user;
}
})
export const getters = () => ({
aliments (state) {
return state.aliments
}
})
I want to diplay a list of aliments in my vue with :
{{ this.$store.state.aliments }}
I call my action like this :
<script>
import { mapGetters, mapActions } from 'vuex'
export default {
computed: {
...mapGetters(['loggedInUser', 'aliments']),
...mapActions(['getListAliments']),
getListAliments() {
return this.$state.aliments
}
}
}
</script>
I don't understand where is my mistake :/
NB: I also tried with a onclick method on a button with a dispatch('aliments/getListAliments')... but doesn't work...
The problem is that you're mapping your actions in the "computed" section of the component, you should map it in the "methods" section !
Hi and Welcome to StackOverflow
to quickly answer to your question, you would call an action as:
this.$store.dispatch('<NAME_OF_ACTION>', payload)
or though a mapActions as
...mapActions(['getListAliments']), // and you call `this.getListAliments(payload)`
or yet
...mapActions({
the_name_you_prefer: 'getListAliments' // and you call `this.the_name_you_prefer(payload)`
}),
for getters, it's the same process, as you already have 2 definitions ['loggedInUser', 'aliments'] you simply call the getter like if it was a computed value <pre>{{ aliments }}</pre>
or when we need to do a bit more (like filtering)
getListAliments() {
return this.$store.getters['aliments']
}
But I can see your store is as we call, one-to-rule-them-all, and because you are using Nuxt, you can actually leverage the module store very easy
as your application grows, you will start store everything in just one store file (the ~/store/index.js file), but you can easily have different stores and instead of what you wrote in index.js it can be easier if you had a file called, taken your example
~/store/food.js with
import axios from 'axios'
export const state = () => ({
aliments: {},
})
export const getters = {
aliments (state) {
return state.aliments
}
}
export const mutations = {
SET_ALIMENTS(state, aliments) {
state.aliments = aliments
}
}
export const actions = {
async getListAliments(commit) {
await axios.get('http://localhost:3080/aliments')
.then((response) => {
console.log(response);
commit('SET_ALIMENTS', response.data);
}).catch(error => {
throw new Error(`${error}`);
})
}
}
BTW, remember that, if you're using Nuxt serverMiddleware, this line
axios.get('http://localhost:3080/aliments')...
would simply be
axios.get('/aliments')...
and to call this store, all you need is to prefix with the filename, like:
...mapActions(['food/getListAliments'])
// or
...mapActions({ getListAliments: 'food/getListAliments' })
// or
this.$store.commit('food/getListAliments', payload)
another naming that could help you along the way:
on your action getListAliments you're actually fetching data from the server, I would change the name to fetchAliments
on your getter aliments you're actually returning the list, I would name it getAllAliments
have fun, Nuxt is amazing and you have a great community on Discord as well for the small things :o)
EDIT
also remember that actions are set in methods
so you can do:
...
export default {
methods: {
...mapActions(['getListAliments]),
},
created() {
this.getListAliments()
}
}
and in your Store action, please make sure you write
async getListAliments({ commit }) { ... }
with curly braces as that's a deconstruction of the property passed
async getListAliments(context) {
...
context.commit(...)
}
I am new to React Context.
I need to call the API in react context to use its data throughout my react application. Also the same API needs to be called on some CRUD operation on various component of react application.
For now I am storing API data in redux which I don't want to store.
Here is what I have tried..
context.js File
import React, { useState, createContext,useEffect } from 'react';
import {getData} from './actionMethods';
const NewContext = createContext();
function newContextProvider(props) {
useEffect(async () => {
const {dataValue} = await getData()
console.log("Data " , dataValue)
}, [])
return (
<NewContext.Provider
value={{
state: {
},
actions: {
}
}}
>
{props.children}
</NewContext.Provider>
);
}
const newContextConsumer = newContext.Consumer;
export { newContextProvider, newContextConsumer, newGridContext };
actionMethods.js
export function getData() {
let config = getInstance('GET', `${prefix}/xyz/list`)
return axios(config).then(res => res.data).catch(err => {
console.log(err)
})
}
when any CRUD operation performs , I need to call the API from the context.js file to get the data from API and store in the context.
Any help would be great.
Thank You.
First we create the Context and pass it an initial value.
In order to fetch data and keep track of the returned value, we create a state inside the component. This component will manage the fetched data and pass it in the Context Provider.
To call an async function inside useEffect we need to wrap it and call it inside useEffect callback.
export const NewContext = createContext({
my_data: {} // Initial value
});
export const NewContextProvider = props => {
const [my_data, setMyData] = useState({});
useEffect(() => {
const fetchMyData = async () => {
const { dataValue } = await getData();
if (dataValue) {
setMyData(dataValue);
} else {
// There was an error fetching the data
}
};
fetchMyData();
}, []);
return (
<NewContext.Provider
value={{
my_data
}}
>
{props.children}
</NewContext.Provider>
);
};
To use this Context in a component we use the useContext hook. Remember that this component needs to be wrapped by the Provider we just created.
import React, { useContext } from "react";
import { NewContext } from "./NewContext"; // The file where the Context was created
export const MyComponent = props => {
const { my_data } = useContext(NewContext);
return //...
};
Let me know if something is not clear.