I am using react to update the state. In my reducer, I am going to update the state but I don't know how to update the state.
My reducer
showSelectedPinnedMovie: function(state,action){
alert(action.payload.length);
for(let i=0; i<state.value.length;i++){
for(let c=0;c<action.payload.length;c++){
alert(action.payload[c]);
if(action.payload[c]=== state.value[i].id){
return {...state,
value: {...state.value[i],"selecttobepinned":"true"}
}
}else {
return {...state,
value: {...state.value[i],"selecttobepinned":"false"}
}
}
}
}
},
I hope some one can solve for me.I am using redux
Related
I have an object in my pinia store like
import { defineStore } from "pinia";
export const useSearchStore = defineStore("store", {
state: () => {
return {
myobj: {
foo: 0,
bar: 2000,
too: 1000,
},
};
},
getters: {
changed() {
// doesn't work
return Object.entries(this.myobj).filter(([key, value]) => value != initialvalue
);
},
},
});
How do I get the initial value to test if the object changed. Or how can I return a filtered object with only those entries different from initial state?
My current workaround:
in a created hook I make a hard copy of the store object I then can compare to. I guess there is a more elegant way...
I had done this (although I do not know if there a better way to avoid cloning without duplicating your initial state).
Define your initial state outside and assign it to a variable as follows;
const initialState = {
foo: 0,
bar: 2000,
too: 1000
}
Then you can use cloning to retain the original state;
export const useSearchStore = defineStore("store", {
state: () => {
return {
myobj: structuredClone(initialState),
};
},
getters: {
changed: (state) => deepEquals(initialState, state.myobj);
},
});
where deepEquals is a method which deep compares the two objects (which you would have to implement). I would use lodash (npm i lodash and npm i #types/lodash --save-dev if you're using TypeScript) for this.
Full code (with lodash);
import { defineStore } from "pinia";
import { cloneDeep, isEqual } from "lodash";
const initialState = {
foo: 0,
bar: 2000,
too: 1000
}
export const useSearchStore = defineStore("store", {
state: () => ({
myobj: cloneDeep(initialState)
}),
getters: {
changed(state) {
return isEqual(initialState, state.myobj);
},
},
});
If you also want the differences between the two you can use the following function (the _ is lodash - import _ from "lodash");
function difference(object, base) {
function changes(object, base) {
return _.transform(object, function (result: object, value, key) {
if (!_.isEqual(value, base[key])) {
result[key] =
_.isObject(value) && _.isObject(base[key])
? changes(value, base[key])
: value;
}
});
}
return changes(object, base);
}
courtesy of https://gist.github.com/Yimiprod/7ee176597fef230d1451
EDIT:
The other way you would do this is to use a watcher to subscribe to changes. The disadvantage to this is that you either have to be OK with your state marked as "changed" if you change back the data to the initial state. Otherwise, you would have to implement a system (perhaps using a stack data structure) to maintain a list of changes so that if two changes which cancel each other out occur then you would remark the state as "unchanged". You would have to keep another variable (boolean) in the state which holds whether the state has been changed/unchanged - but this would be more complicated to implement and (depending on your use case) not worth it.
I am storing if a checkbox is checked or not using AsyncStorage. When I reload the app, I see from logs that inside the asynchronous variable the correct information is stored. But, it is loaded after componentWillMount. Because of that, the checkbox does not appear checked, as it should be.
I think a good workaround will be to change the checkbox properties inside the asynchronous function. Do you think that would be a good solution? Do you have other suggestions for showing the correct checkbox value?
My code:
constructor(props) {
super(props)
this.state = {isChecked: false}
this.switchStatus = this.switchStatus.bind(this)
}
async getCache(key) {
try {
const status = await AsyncStorage.getItem(key);
if (status == null)
status = false
console.log("my async status is " + status)
return status
} catch(error) {
console.log("error", e)
}
}
componentWillMount(){
// key string depends on the object selected to be checked
const key = "status_" + this.props.data.id.toString()
this.getCache = this.getCache.bind(this)
this.setState({isChecked: (this.getCache(key) == 'true')})
console.log("my state is" + this.state.isChecked)
}
switchStatus () {
const newStatus = this.state.isChecked == false ? true : false
AsyncStorage.setItem("status_" + this.props.data.id.toString(), newStatus.toString());
console.log("hi my status is " + newStatus)
this.setState({isChecked: newStatus})
}
render({ data, onPress} = this.props) {
const {id, title, checked} = data
return (
<ListItem button onPress={onPress}>
<CheckBox
style={{padding: 1}}
onPress={(this.switchStatus}
checked={this.state.isChecked}
/>
<Body>
<Text>{title}</Text>
</Body>
<Right>
<Icon name="arrow-forward" />
</Right>
</ListItem>
)
}
There is no difference if I put everything in componentWillMount in the constructor.
Thank you for your answers. I am pretty sure await will work too, but I solved the problem before getting an answer. What I did was set the state to false in the beginning, and then update it in getCache. This way, it will always be set after getting the information from the local phone storage.
async getCache(key) {
try {
let status = await AsyncStorage.getItem(key);
if (status == null) {
status = false
}
this.setState({ isChecked: (status == 'true') })
} catch(e) {
console.log("error", e);
}
}
you make use of the async - await, but you are not waiting for your method in componentWillMount(). Try this:
componentWillMount(){
// key string depends on the object selected to be checked
const key = "status_" + this.props.data.id.toString()
this.getCache = await this.getCache.bind(this) // <-- Missed await
this.setState({isChecked: (this.getCache(key) == 'true')})
console.log("my state is" + this.state.isChecked)
}
The return value of an async function is a Promise object. So you have to use then to access the resolved value of getCache. Change your code to the following and it should work.
componentWillMount(){
// key string depends on the object selected to be checked
const key = "status_" + this.props.data.id.toString();
this.getCache(key).then(status => {
this.setState({ isChecked: status === true });
})
}
I am using normalizr to organize my redux-store state.
Let's say that I have normalized todo-list:
{
result: [1, 2],
entities: {
todo: {
1: {
id: 1,
title: 'Do something'
},
2: {
id: 2,
title: 'Second todo'
}
}
}
}
Then I would like to implement addTodo action. I need to have an id in todo object, so I generate a random one:
function todoReducer(state, action) {
if(action.type == ADD_TODO) {
const todoId = generateUUID();
return {
result: [...state.result, todoId],
enitities: {
todos: {
...state.entities.todos,
[todoId]: action.todo
}
}
}
}
//...other handlers...
return state;
}
But the problem is that eventually all data will be saved to server and generated id should be replaced with real server-assigned id. Now I merge them like this:
//somewhere in reducer...
if(action.type === REPLACE_TODO) {
// copy todos map, add new entity, remove old
const todos = {
...state.entities.todos
[action.todo.id]: action.todo
};
delete todos[action.oldId];
// update results array as well
const result = state.result.filter(id => id !== oldId).concat(action.todo.id);
// return new state
return {entities: {todos}, result};
}
It seems to be a working solution, but there also a lot of overhead. Do you know any way to simplify this and don't make REPLACE_TODO operation?
React document states that the render function should be pure which mean it should not use this.setState in it .However, I believe when the state is depended on 'remote' i.e. result from ajax call.The only solution is setState() inside a render function
In my case.Our users can should be able to log in. After login, We also need check the user's access (ajax call )to decide how to display the page.The code is something like this
React.createClass({
render:function(){
if(this.state.user.login)
{
//do not call it twice
if(this.state.callAjax)
{
var self=this
$.ajax{
success:function(result)
{
if(result==a)
{self.setState({callAjax:false,hasAccess:a})}
if(result==b)
{self.setState({callAjax:false,hasAccess:b})}
}
}
}
if(this.state.hasAccess==a) return <Page />
else if(this.state.hasAccess==a) return <AnotherPage />
else return <LoadingPage />
}
else
{
return <div>
<button onClick:{
function(){this.setState({user.login:true})}
}>
LOGIN
</button>
</div>
}
}
})
The ajax call can not appear in componentDidMount because when user click LOGIN button the page is re-rendered and also need ajax call .So I suppose the only place to setState is inside the render function which breach the React principle
Any better solutions ? Thanks in advance
render should always remain pure. It's a very bad practice to do side-effecty things in there, and calling setState is a big red flag; in a simple example like this it can work out okay, but it's the road to highly unmaintainable components, plus it only works because the side effect is async.
Instead, think about the various states your component can be in — like you were modeling a state machine (which, it turns out, you are):
The initial state (user hasn't clicked button)
Pending authorization (user clicked login, but we don't know the result of the Ajax request yet)
User has access to something (we've got the result of the Ajax request)
Model this out with your component's state and you're good to go.
React.createClass({
getInitialState: function() {
return {
busy: false, // waiting for the ajax request
hasAccess: null, // what the user has access to
/**
* Our three states are modeled with this data:
*
* Pending: busy === true
* Has Access: hasAccess !== null
* Initial/Default: busy === false, hasAccess === null
*/
};
},
handleButtonClick: function() {
if (this.state.busy) return;
this.setState({ busy: true }); // we're waiting for ajax now
this._checkAuthorization();
},
_checkAuthorization: function() {
$.ajax({
// ...,
success: this._handleAjaxResult
});
},
_handleAjaxResult: function(result) {
if(result === a) {
this.setState({ hasAccess: a })
} else if(result ===b ) {
this.setState({ hasAccess: b })
}
},
render: function() {
// handle each of our possible states
if (this.state.busy) { // the pending state
return <LoadingPage />;
} else if (this.state.hasAccess) { // has access to something
return this._getPage(this.state.hasAccess);
} else {
return <button onClick={this.handleButtonClick}>LOGIN</button>;
}
},
_getPage: function(access) {
switch (access) {
case a:
return <Page />;
case b:
return <AnotherPage />;
default:
return <SomeDefaultPage />;
}
}
});
I've encountered a problem with rendering some elements in React.
(I use ImmutableJS)
renderComponents: function(components) {
if(components.isEmpty()) return [];
var table = [];
components.map(function(component) {
table.push(<ComponentTableElement key={ component.get('id') } data={ component } />);
if(component.has('children')) {
var children = component.get('children');
table.concat(this.renderComponents(children));
}
});
return table;
},
As I looked for error, I found that this.renderComponents(children) doesn't return anything at all and the code somehow stops.
I mean before that line everything works ok, but then after this line, when i try to console.log something, it doesn't show up. And it doesn't even reach return table.
So what is wrong with that code?
In the context of the function you pass to map, this refers to the window object, not to the current component instance, so this.renderComponents is undefined when you try to call it.
components.map(function(component) {
this === window;
});
You can pass a value to use as this in the body of your function as the second parameter of Array::map.
components.map(function(component) {
table.push(<ComponentTableElement key={ component.get('id') } data={ component } />);
if(component.has('children')) {
var children = component.get('children');
// here, `this` refers to the component instance
table.concat(this.renderComponents(children));
}
}, this);
If you're using ES6, you can also use fat-arrow functions, which are automatically bound to this.
components.map((component) => {
table.push(<ComponentTableElement key={ component.get('id') } data={ component } />);
if(component.has('children')) {
var children = component.get('children');
// here, `this` refers to the component instance
table.concat(this.renderComponents(children));
}
});